#define GAME_DEFINITION #include "Game.h" #include #include #include #include #include "ButtonGlyphs.h" #include "Constants.h" #include "CustomLevels.h" #include "DeferCallbacks.h" #include "Editor.h" #include "Entity.h" #include "Enums.h" #include "FileSystemUtils.h" #include "GlitchrunnerMode.h" #include "Graphics.h" #include "LevelDebugger.h" #include "Localization.h" #include "LocalizationStorage.h" #include "KeyPoll.h" #include "MakeAndPlay.h" #include "Map.h" #include "Music.h" #include "Network.h" #include "RoomnameTranslator.h" #include "Screen.h" #include "Script.h" #include "Unused.h" #include "UTF8.h" #include "UtilityClass.h" #include "VFormat.h" #include "Vlogging.h" #include "XMLUtils.h" static bool GetButtonFromString(const char *pText, SDL_GameControllerButton *button) { if (*pText == '0' || *pText == 'a' || *pText == 'A') { *button = SDL_CONTROLLER_BUTTON_A; return true; } if (SDL_strcmp(pText, "1") == 0 || *pText == 'b' || *pText == 'B') { *button = SDL_CONTROLLER_BUTTON_B; return true; } if (*pText == '2' || *pText == 'x' || *pText == 'X') { *button = SDL_CONTROLLER_BUTTON_X; return true; } if (*pText == '3' || *pText == 'y' || *pText == 'Y') { *button = SDL_CONTROLLER_BUTTON_Y; return true; } if (*pText == '4' || SDL_strcasecmp(pText, "BACK") == 0) { *button = SDL_CONTROLLER_BUTTON_BACK; return true; } if (*pText == '5' || SDL_strcasecmp(pText, "GUIDE") == 0) { *button = SDL_CONTROLLER_BUTTON_GUIDE; return true; } if (*pText == '6' || SDL_strcasecmp(pText, "START") == 0) { *button = SDL_CONTROLLER_BUTTON_START; return true; } if (*pText == '7' || SDL_strcasecmp(pText, "LS") == 0) { *button = SDL_CONTROLLER_BUTTON_LEFTSTICK; return true; } if (*pText == '8' || SDL_strcasecmp(pText, "RS") == 0) { *button = SDL_CONTROLLER_BUTTON_RIGHTSTICK; return true; } if (*pText == '9' || SDL_strcasecmp(pText, "LB") == 0) { *button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; return true; } if (SDL_strcmp(pText, "10") == 0 || SDL_strcasecmp(pText, "RB") == 0) { *button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; return true; } return false; } // Unfortunate forward-declare... My hands are pretty tied static void loadthissummary( const char* filename, struct Game::Summary* summary, tinyxml2::XMLDocument& doc ); static struct Game::Summary get_summary( const char* filename, const char* savename, tinyxml2::XMLDocument& doc ) { tinyxml2::XMLHandle hDoc(&doc); struct Game::Summary summary; SDL_zero(summary); if (!FILESYSTEM_loadTiXml2Document(filename, doc)) { vlog_info("%s not found", savename); return summary; } loadthissummary(savename, &summary, doc); return summary; } void Game::init(void) { SDL_strlcpy(magic, "[vVvVvV]game", sizeof(magic)); roomx = 0; roomy = 0; prevroomx = 0; prevroomy = 0; saverx = 0; savery = 0; savecolour = 0; mutebutton = 0; muted = false; musicmuted = false; musicmutebutton = 0; glitchrunkludge = false; gamestate = TITLEMODE; prevgamestate = TITLEMODE; hascontrol = true; jumpheld = false; advancetext = false; jumppressed = 0; gravitycontrol = 0; teleport = false; edteleportent = 0; //Added in the port! companion = 0; quickrestartkludge = false; tapleft = 0; tapright = 0; press_right = false; press_left = false; press_action = false; press_map = false; press_interact = false; interactheld = false; separate_interact = false; mapheld = false; pausescript = false; completestop = false; activeactivity = -1; act_fade = 0; prev_act_fade = 0; backgroundtext = false; startscript = false; inintermission = false; alarmon = false; alarmdelay = 0; blackout = false; creditposx = 0; creditposy = 0; creditposdelay = 0; oldcreditposx = 0; useteleporter = false; teleport_to_teleporter = 0; activetele = false; readytotele = 0; oldreadytotele = 0; activity_r = 0; activity_g = 0; activity_b = 0; activity_y = 0; creditposition = 0; oldcreditposition = 0; bestgamedeaths = -1; //Accessibility Options colourblindmode = false; noflashingmode = false; slowdown = 30; nodeathmode = false; nocutscenes = false; ndmresultcrewrescued = 0; ndmresulttrinkets = 0; ndmresulthardestroom.clear(); ndmresulthardestroom_x = hardestroom_x; ndmresulthardestroom_y = hardestroom_y; ndmresulthardestroom_specialname = false; nodeatheligible = false; customcol=0; SDL_memset(crewstats, false, sizeof(crewstats)); SDL_memset(ndmresultcrewstats, false, sizeof(ndmresultcrewstats)); SDL_memset(besttimes, -1, sizeof(besttimes)); SDL_memset(bestframes, -1, sizeof(bestframes)); SDL_memset(besttrinkets, -1, sizeof(besttrinkets)); SDL_memset(bestlives, -1, sizeof(bestlives)); SDL_memset(bestrank, -1, sizeof(bestrank)); crewstats[0] = true; lastsaved = 0; //Menu stuff initiliased here: SDL_memset(unlock, false, sizeof(unlock)); SDL_memset(unlocknotify, false, sizeof(unlock)); currentmenuoption = 0; menutestmode = false; current_credits_list_index = 0; translator_credits_pagenum = 0; menuxoff = 0; menuyoff = 0; menucountdown = 0; levelpage=0; playcustomlevel=0; silence_settings_error = false; deathcounts = 0; gameoverdelay = 0; framecounter = 0; seed_use_sdl_getticks = false; editor_disabled = false; resetgameclock(); gamesaved = false; gamesavefailed = false; savetime = "00:00"; savetrinkets = 0; /* These are only used some of the time. */ saveframes = 0; saveseconds = 0; intimetrial = false; timetrialcountdown = 0; timetrialshinytarget = 0; timetrialparlost = false; timetrialpar = 0; timetrialcheater = false; timetrialresulttime = 0; timetrialresultframes = 0; timetrialresultshinytarget = 0; timetrialresulttrinkets = 0; timetrialresultpar = 0; timetrialresultdeaths = 0; start_translator_exploring = false; translator_exploring = false; translator_exploring_allowtele = false; translator_cutscene_test = false; totalflips = 0; hardestroom = "Welcome Aboard"; hardestroomdeaths = 0; hardestroom_x = 13; hardestroom_y = 5; hardestroom_specialname = false; hardestroom_finalstretch = false; currentroomdeaths=0; inertia = 1.1f; swnmode = false; swntimer = 0; swngame = SWN_NONE; // Not playing sine wave ninja! swnstate = 0; swnstate2 = 0; swnstate3 = 0; swnstate4 = 0; swndelay = 0; swndeaths = 0; supercrewmate = false; scmhurt = false; scmprogress = 0; swncolstate = 0; swncoldelay = 0; swnrecord = 0; swnbestrank = 0; swnrank = 0; swnmessage = 0; clearcustomlevelstats(); saveFilePath = FILESYSTEM_getUserSaveDirectory(); tinyxml2::XMLDocument doc; last_quicksave = get_summary("saves/qsave.vvv", "qsave.vvv", doc); tinyxml2::XMLDocument docTele; last_telesave = get_summary("saves/tsave.vvv", "tsave.vvv", doc); screenshake = flashlight = 0 ; stat_trinkets = 0; state = 1; statedelay = 0; statelocked = false; //updatestate(); skipfakeload = false; ghostsenabled = false; cliplaytest = false; playx = 0; playy = 0; playrx = 0; playry = 0; playgc = 0; fadetomenu = false; fadetomenudelay = 0; fadetolab = false; fadetolabdelay = 0; over30mode = true; showingametimer = false; ingame_titlemode = false; ingame_editormode = false; kludge_ingametemp = Menu::mainmenu; slidermode = SLIDER_NONE; disablepause = false; disableaudiopause = false; disabletemporaryaudiopause = true; inputdelay = false; old_skip_message_timer = 0; skip_message_timer = 0; old_mode_indicator_timer = 0; mode_indicator_timer = 0; old_screenshot_border_timer = 0; screenshot_border_timer = 0; screenshot_saved_success = false; #ifdef __ANDROID__ checkpoint_saving = true; #else checkpoint_saving = false; #endif setdefaultcontrollerbuttons(); } void Game::setdefaultcontrollerbuttons(void) { if (controllerButton_flip.size() < 1) { controllerButton_flip.push_back(SDL_CONTROLLER_BUTTON_A); } if (controllerButton_map.size() < 1) { controllerButton_map.push_back(SDL_CONTROLLER_BUTTON_Y); } if (controllerButton_esc.size() < 1) { controllerButton_esc.push_back(SDL_CONTROLLER_BUTTON_B); } if (controllerButton_restart.size() < 1) { controllerButton_restart.push_back(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); } if (controllerButton_interact.size() < 1) { controllerButton_interact.push_back(SDL_CONTROLLER_BUTTON_X); } /* If one of the arrays was empty, and others weren't, we might now have conflicts... * A crucial one is if the ACTION button is also "ESC", because then you can't * fix it with just a controller anymore, which might make the game unplayable. * This is similar to updatebuttonmappings() in Input.cpp, except less... complete? */ for (size_t f = 0; f < controllerButton_flip.size(); f++) { for (size_t e = 0; e < controllerButton_esc.size(); e++) { if (controllerButton_flip[f] == controllerButton_esc[e]) { controllerButton_esc.erase(controllerButton_esc.begin() + e); break; } } } } void Game::lifesequence(void) { if (lifeseq > 0) { int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].invis = false; if (lifeseq == 2) obj.entities[i].invis = true; if (lifeseq == 6) obj.entities[i].invis = true; if (lifeseq >= 8) obj.entities[i].invis = true; } if (lifeseq > 5) gravitycontrol = savegc; lifeseq--; if (INBOUNDS_VEC(i, obj.entities) && (lifeseq <= 0 || noflashingmode)) { obj.entities[i].invis = false; } } } void Game::clearcustomlevelstats(void) { //just clearing the array customlevelstats.clear(); } void Game::updatecustomlevelstats(std::string clevel, int cscore) { if (!map.custommodeforreal) { /* We are playtesting, don't update level stats */ return; } if (clevel.find("levels/") != std::string::npos) { clevel = clevel.substr(7); } if (customlevelstats.count(clevel) == 0 || cscore > customlevelstats[clevel]) { customlevelstats[clevel] = cscore; } savecustomlevelstats(); } void Game::deletecustomlevelstats(void) { customlevelstats.clear(); if (!FILESYSTEM_delete("saves/levelstats.vvv")) { vlog_error("Error deleting levelstats.vvv"); } } #define LOAD_ARRAY_RENAME(ARRAY_NAME, DEST) \ if (SDL_strcmp(pKey, #ARRAY_NAME) == 0 && pText[0] != '\0') \ { \ /* We're loading in 32-bit integers. If we need more than 16 chars, * something is seriously wrong */ \ char buffer[16]; \ size_t start = 0; \ size_t i = 0; \ \ while (next_split_s(buffer, sizeof(buffer), &start, pText, ',')) \ { \ if (i >= SDL_arraysize(DEST)) \ { \ break; \ } \ \ DEST[i] = help.Int(buffer); \ ++i; \ } \ } #define LOAD_ARRAY(ARRAY_NAME) LOAD_ARRAY_RENAME(ARRAY_NAME, ARRAY_NAME) void Game::loadcustomlevelstats(void) { tinyxml2::XMLDocument doc; tinyxml2::XMLHandle hDoc(&doc); if (!FILESYSTEM_loadTiXml2Document("saves/levelstats.vvv", doc)) { //No levelstats file exists; start new customlevelstats.clear(); savecustomlevelstats(); return; } if (doc.Error()) { vlog_error("Error parsing levelstats.vvv: %s", doc.ErrorStr()); return; } customlevelstats.clear(); tinyxml2::XMLElement* pElem; tinyxml2::XMLElement* firstElement; firstElement = hDoc .FirstChildElement() .FirstChildElement("Data") .FirstChildElement() .ToElement(); // First pass, look for the new system of storing stats // If they don't exist, then fall back to the old system for (pElem = firstElement; pElem != NULL; pElem = pElem->NextSiblingElement()) { const char* pKey = pElem->Value(); const char* pText = pElem->GetText(); if (pText == NULL) { pText = ""; } if (SDL_strcmp(pKey, "stats") == 0) { bool file_has_duplicates = false; for (tinyxml2::XMLElement* stat_el = pElem->FirstChildElement(); stat_el; stat_el = stat_el->NextSiblingElement()) { int score = 0; std::string name; if (stat_el->GetText() != NULL) { score = help.Int(stat_el->GetText()); } if (stat_el->Attribute("name")) { name = stat_el->Attribute("name"); } int existing = customlevelstats.count(name); if (existing > 0) { file_has_duplicates = true; } if (existing == 0 || score > customlevelstats[name]) { customlevelstats[name] = score; } } if (file_has_duplicates) { /* This might be really inflated, so simply save the map we have now, * so we don't have to keep loading a 90 MB file. */ savecustomlevelstats(); } return; } } // Since we're still here, we must be on the old system std::vector customlevelnames; std::vector customlevelscores; for (pElem = firstElement; pElem; pElem=pElem->NextSiblingElement()) { const char* pKey = pElem->Value(); const char* pText = pElem->GetText() ; if(pText == NULL) { pText = ""; } if (SDL_strcmp(pKey, "customlevelscore") == 0 && pText[0] != '\0') { char buffer[16]; size_t start = 0; while (next_split_s(buffer, sizeof(buffer), &start, pText, ',')) { customlevelscores.push_back(help.Int(buffer)); } } if (SDL_strcmp(pKey, "customlevelstats") == 0 && pText[0] != '\0') { size_t start = 0; size_t len = 0; size_t prev_start = 0; while (next_split(&start, &len, &pText[start], '|')) { customlevelnames.push_back(std::string(&pText[prev_start], len)); prev_start = start; } } } // If the two arrays happen to differ in length, just go with the smallest one for (size_t i = 0; i < SDL_min(customlevelnames.size(), customlevelscores.size()); i++) { const std::string& name = customlevelnames[i]; const int score = customlevelscores[i]; if (customlevelstats.count(name) == 0 || score > customlevelstats[name]) { customlevelstats[name] = score; } } } void Game::savecustomlevelstats(void) { tinyxml2::XMLDocument doc; bool already_exists = FILESYSTEM_loadTiXml2Document("saves/levelstats.vvv", doc); if (!already_exists) { vlog_info("No levelstats.vvv found. Creating new file"); } else if (doc.Error()) { vlog_error("Error parsing existing levelstats.vvv: %s", doc.ErrorStr()); vlog_info("Creating new levelstats.vvv"); } xml::update_declaration(doc); tinyxml2::XMLElement * root = xml::update_element(doc, "Levelstats"); xml::update_comment(root, " Levelstats Save file "); tinyxml2::XMLElement * msgs = xml::update_element(root, "Data"); int numcustomlevelstats = customlevelstats.size(); if(numcustomlevelstats>=200)numcustomlevelstats=199; xml::update_tag(msgs, "numcustomlevelstats", numcustomlevelstats); std::string customlevelscorestr; std::string customlevelstatsstr; std::map::iterator iter; for (iter = customlevelstats.begin(); iter != customlevelstats.end(); iter++) { customlevelscorestr += help.String(iter->second) + ","; customlevelstatsstr += iter->first + "|"; } xml::update_tag(msgs, "customlevelscore", customlevelscorestr.c_str()); xml::update_tag(msgs, "customlevelstats", customlevelstatsstr.c_str()); // New system tinyxml2::XMLElement* msg = xml::update_element_delete_contents(msgs, "stats"); tinyxml2::XMLElement* stat_el; for (iter = customlevelstats.begin(); iter != customlevelstats.end(); iter++) { stat_el = doc.NewElement("stat"); stat_el->SetAttribute("name", iter->first.c_str()); stat_el->LinkEndChild(doc.NewText(help.String(iter->second).c_str())); msg->LinkEndChild(stat_el); } if(FILESYSTEM_saveTiXml2Document("saves/levelstats.vvv", doc)) { vlog_info("Level stats saved"); } else { vlog_error("Could Not Save level stats!"); vlog_error("Failed: %s%s", saveFilePath, "levelstats.vvv"); } } void Game::levelcomplete_textbox(void) { graphics.createtextboxflipme("", -1, 12, TEXT_COLOUR("cyan")); graphics.addline(" "); graphics.addline(""); graphics.addline(""); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); graphics.textboxcenterx(); graphics.setimage(TEXTIMAGE_LEVELCOMPLETE); graphics.setlinegap(0); graphics.textboxapplyposition(); } static void compute_crewmate_textbox(textboxclass* THIS) { THIS->lines.clear(); THIS->addline(""); const int extra_cjk_height = (font::height(PR_FONT_INTERFACE) * 4) - 32; THIS->yp = 64 + 8 + 16 - extra_cjk_height/2; /* This is a special case for wrapping, we MUST have two lines. * So just make sure it can't fit in one line. */ const char* text = loc::gettext("You have rescued a crew member!"); std::string wrapped = font::string_wordwrap_balanced(PR_FONT_INTERFACE, text, font::len(PR_FONT_INTERFACE, text)-1); size_t startline = 0; size_t newline; do { size_t pos_n = wrapped.find('\n', startline); size_t pos_p = wrapped.find('|', startline); newline = SDL_min(pos_n, pos_p); THIS->addline(wrapped.substr(startline, newline-startline)); startline = newline+1; } while (newline != std::string::npos); THIS->addline(""); THIS->centertext(); float spaces_per_8 = font::len(PR_FONT_INTERFACE, " ")/8.0f; THIS->pad(SDL_ceilf(5/spaces_per_8), SDL_ceilf(2/spaces_per_8)); if (!THIS->sprites.empty()) { THIS->sprites[0].y = 12 + extra_cjk_height/2; } } void Game::crewmate_textbox(const int color) { const int extra_cjk_height = (font::height(PR_FONT_INTERFACE) * 4) - 32; graphics.createtextboxflipme("", -1, 64 + 8 + 16, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.addsprite(14, 12 + extra_cjk_height/2, 0, color); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, compute_crewmate_textbox); graphics.setlinegap(0); graphics.textboxapplyposition(); } static void compute_remaining_textbox(textboxclass* THIS) { extern Game game; const int remaining = 6 - game.crewrescued(); char buffer[SCREEN_WIDTH_CHARS + 1]; if (remaining > 0) { loc::gettext_plural_fill(buffer, sizeof(buffer), "{n_crew|wordy} remain", "{n_crew|wordy} remains", "n_crew:int", remaining); } else { SDL_strlcpy(buffer, loc::gettext("All Crew Members Rescued!"), sizeof(buffer)); } THIS->lines.clear(); THIS->lines.push_back(buffer); // In CJK, the "You have rescued" box becomes so big we should lower this one a bit... const int cjk_lowering = font::height(PR_FONT_INTERFACE) - 8; THIS->yp = 128 + 16 + cjk_lowering; THIS->pad(2, 2); } void Game::remaining_textbox(void) { graphics.createtextboxflipme("", -1, 128 + 16, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, compute_remaining_textbox); graphics.textboxapplyposition(); } static void compute_actionprompt_textbox(textboxclass* THIS) { THIS->lines.clear(); char buffer[SCREEN_WIDTH_CHARS + 1]; vformat_buf( buffer, sizeof(buffer), loc::gettext("Press {button} to continue"), "button:but", vformat_button(ActionSet_InGame, Action_InGame_ACTION) ); THIS->lines.push_back(buffer); THIS->pad(1, 1); } void Game::actionprompt_textbox(void) { graphics.createtextboxflipme("", -1, 196, TEXT_COLOUR("cyan")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, compute_actionprompt_textbox); graphics.textboxapplyposition(); } static void savetele_textbox_success(textboxclass* THIS) { THIS->lines.clear(); THIS->lines.push_back(loc::gettext("Game Saved")); THIS->pad(3, 3); } static void save_textbox_fail(textboxclass* THIS) { THIS->lines.clear(); THIS->lines.push_back(loc::gettext("ERROR: Could not save game!")); THIS->wrap(2); THIS->pad(1, 1); } void Game::show_save_fail(void) { graphics.createtextboxflipme("", -1, 12, TEXT_COLOUR("red")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtimer(50); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, save_textbox_fail); } void Game::checkpoint_save(void) { if (checkpoint_saving && !inspecial() && (!map.custommode || (map.custommode && map.custommodeforreal)) && !cliplaytest) { bool success = map.custommode ? customsavequick(cl.ListOfMetaData[playcustomlevel].filename) : savequick(); gamesaved = success; gamesavefailed = !success; if (gamesavefailed) { show_save_fail(); graphics.textboxapplyposition(); } } } void Game::savetele_textbox(void) { if (inspecial() || map.custommode) { return; } if (savetele()) { graphics.createtextboxflipme("", -1, 12, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtimer(25); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, savetele_textbox_success); } else { show_save_fail(); } graphics.textboxapplyposition(); } static void wasd_textbox(textboxclass* THIS) { THIS->lines.clear(); THIS->lines.push_back(BUTTONGLYPHS_get_wasd_text()); THIS->wrap(4); THIS->centertext(); THIS->pad(2, 2); } static void flip_textbox(textboxclass* THIS) { THIS->lines.clear(); char buffer[SCREEN_WIDTH_CHARS*3 + 1]; vformat_buf( buffer, sizeof(buffer), loc::gettext("Press {button} to flip"), "button:but", vformat_button(ActionSet_InGame, Action_InGame_ACTION) ); THIS->lines.push_back(buffer); THIS->wrap(4); THIS->centertext(); THIS->pad(2, 2); } static void arrowkey_textbox(textboxclass* THIS) { THIS->lines.clear(); THIS->lines.push_back(loc::gettext("If you prefer, you can press UP or DOWN instead of ACTION to flip.")); THIS->wrap(2); THIS->centertext(); THIS->pad(1, 1); } static void map_textbox(textboxclass* THIS) { THIS->lines.clear(); char buffer[SCREEN_WIDTH_CHARS*3 + 1]; vformat_buf( buffer, sizeof(buffer), loc::gettext("Press {button} to view map and quicksave"), "button:but", vformat_button(ActionSet_InGame, Action_InGame_Map) ); THIS->lines.push_back(buffer); THIS->wrap(4); THIS->centertext(); THIS->pad(2, 2); } static void im1_instructions_textbox1(textboxclass* THIS) { extern Game game; THIS->lines.clear(); // Intermission 1 instructional textbox, depends on last saved const char* floorceiling = graphics.flipmode ? "ceiling" : "floor"; const char* crewmate; switch (game.lastsaved) { case 2: crewmate = "Vitellary"; break; case 3: crewmate = "Vermilion"; break; case 4: crewmate = "Verdigris"; break; case 5: crewmate = "Victoria"; break; default: crewmate = "your companion"; } char english[SCREEN_WIDTH_TILES*3 + 1]; /* ASCII only */ vformat_buf(english, sizeof(english), "When you're standing on the {floorceiling}, {crewmate} will try to walk to you.", "floorceiling:str, crewmate:str", floorceiling, crewmate ); THIS->lines.push_back(loc::gettext(english)); THIS->wrap(2); THIS->padtowidth(36*8); } static void im1_instructions_textbox2(textboxclass* THIS) { extern Game game; THIS->lines.clear(); // Intermission 1 instructional textbox, depends on last saved const char* floorceiling = graphics.flipmode ? "ceiling" : "floor"; const char* crewmate; switch (game.lastsaved) { case 2: crewmate = "Vitellary"; break; case 3: crewmate = "Vermilion"; break; case 4: crewmate = "Verdigris"; break; case 5: crewmate = "Victoria"; break; default: crewmate = "your companion"; } char english[SCREEN_WIDTH_TILES*3 + 1]; /* ASCII only */ vformat_buf(english, sizeof(english), "When you're NOT standing on the {floorceiling}, {crewmate} will stop and wait for you.", "floorceiling:str, crewmate:str", floorceiling, crewmate ); THIS->lines.push_back(loc::gettext(english)); THIS->wrap(2); THIS->padtowidth(36*8); } static void im1_instructions_textbox3(textboxclass* THIS) { extern Game game; THIS->lines.clear(); // Intermission 1 instructional textbox, depends on last saved const char* english; switch (game.lastsaved) { case 2: case 3: case 4: english = "You can't continue to the next room until he is safely across."; break; case 5: english = "You can't continue to the next room until she is safely across."; break; default: english = "You can't continue to the next room until they are safely across."; } THIS->lines.push_back(loc::gettext(english)); THIS->wrap(2); THIS->padtowidth(36*8); } /* Also used in foundtrinket() script command. */ void foundtrinket_textbox1(textboxclass* THIS) { THIS->lines.clear(); THIS->lines.push_back(loc::gettext("Congratulations!\n\nYou have found a shiny trinket!")); THIS->wrap(2); THIS->centertext(); THIS->pad(1, 1); } /* Also used in foundtrinket() script command. */ void foundtrinket_textbox2(textboxclass* THIS) { extern Game game; THIS->lines.clear(); const int max_trinkets = map.custommode ? cl.numtrinkets() : 20; char buffer[SCREEN_WIDTH_CHARS + 1]; vformat_buf( buffer, sizeof(buffer), loc::gettext("{n_trinkets|wordy} out of {max_trinkets|wordy}"), "n_trinkets:int, max_trinkets:int", game.trinkets(), max_trinkets ); THIS->lines.push_back(buffer); if (INBOUNDS_VEC(THIS->other_textbox_index, graphics.textboxes) && &graphics.textboxes[THIS->other_textbox_index] != THIS) { THIS->yp = 95 + graphics.textboxes[THIS->other_textbox_index].h; } THIS->wrap(2); THIS->centertext(); THIS->pad(1, 1); } static void foundcrewmate_textbox1(textboxclass* THIS) { THIS->lines.clear(); THIS->lines.push_back(loc::gettext("Congratulations!\n\nYou have found a lost crewmate!")); THIS->wrap(2); THIS->centertext(); THIS->pad(1, 1); } static void foundcrewmate_textbox2(textboxclass* THIS) { extern Game game; THIS->lines.clear(); const int num_remaining = cl.numcrewmates() - game.crewmates(); if (num_remaining == 0) { THIS->lines.push_back(loc::gettext("All crewmates rescued!")); } else { char buffer[SCREEN_WIDTH_CHARS + 1]; loc::gettext_plural_fill( buffer, sizeof(buffer), "{n_crew|wordy} remain", "{n_crew|wordy} remains", "n_crew:int", num_remaining ); THIS->lines.push_back(buffer); } if (INBOUNDS_VEC(THIS->other_textbox_index, graphics.textboxes) && &graphics.textboxes[THIS->other_textbox_index] != THIS) { THIS->yp = 95 + graphics.textboxes[THIS->other_textbox_index].h; } THIS->wrap(4); THIS->centertext(); THIS->pad(2, 2); } static void gamecomplete_textbox2(textboxclass* THIS) { THIS->lines.clear(); THIS->lines.push_back(loc::gettext("All Crew Members Rescued!")); } static void gamecomplete_textbox3(textboxclass* THIS) { THIS->lines.clear(); const char* label = loc::gettext("Trinkets Found:"); THIS->lines.push_back(label); THIS->xp = 170 - font::len(PR_FONT_INTERFACE, label); } static void gamecomplete_textbox4(textboxclass* THIS) { extern Game game; THIS->lines.clear(); char buffer[SCREEN_WIDTH_CHARS + 1]; vformat_buf(buffer, sizeof(buffer), loc::gettext("{gamecomplete_n_trinkets|wordy}"), "gamecomplete_n_trinkets:int", game.trinkets() ); THIS->lines.push_back(buffer); } static void gamecomplete_textbox5(textboxclass* THIS) { THIS->lines.clear(); const char* label = loc::gettext("Game Time:"); THIS->lines.push_back(label); THIS->xp = 170 - font::len(PR_FONT_INTERFACE, label); } static void gamecomplete_textbox6(textboxclass* THIS) { extern Game game; THIS->lines.clear(); char buffer[SCREEN_WIDTH_CHARS + 1]; help.format_time(buffer, sizeof(buffer), game.saveseconds, game.saveframes, true); THIS->lines.push_back(buffer); } static void gamecomplete_textbox7(textboxclass* THIS) { THIS->lines.clear(); const char* label = loc::gettext("Total Flips:"); THIS->lines.push_back(label); THIS->xp = 170 - font::len(PR_FONT_INTERFACE, label); } static void gamecomplete_textbox9(textboxclass* THIS) { THIS->lines.clear(); const char* label = loc::gettext("Total Deaths:"); THIS->lines.push_back(label); THIS->xp = 170 - font::len(PR_FONT_INTERFACE, label); } static void gamecomplete_textbox11(textboxclass* THIS) { extern Game game; THIS->lines.clear(); char buffer[SCREEN_WIDTH_CHARS + 1]; loc::gettext_plural_fill( buffer, sizeof(buffer), "Hardest Room (with {n_deaths} deaths)", "Hardest Room (with {n_deaths} death)", "n_deaths:int", game.hardestroomdeaths ); THIS->lines.push_back(buffer); } static void gamecomplete_textbox12(textboxclass* THIS) { extern Game game; THIS->lines.clear(); THIS->lines.push_back( loc::gettext_roomname( map.custommode, game.hardestroom_x, game.hardestroom_y, game.hardestroom.c_str(), game.hardestroom_specialname ) ); } void Game::setstate(const int gamestate) { if (!statelocked || GlitchrunnerMode_less_than_or_equal(Glitchrunner2_2)) { state = gamestate; } } void Game::incstate(void) { if (!statelocked || GlitchrunnerMode_less_than_or_equal(Glitchrunner2_2)) { state++; } } void Game::setstatedelay(const int delay) { if (!statelocked || GlitchrunnerMode_less_than_or_equal(Glitchrunner2_2)) { statedelay = delay; } } void Game::lockstate(void) { statelocked = true; } void Game::unlockstate(void) { statelocked = false; } void Game::updatestate(void) { statedelay--; if (statedelay <= 0) { statedelay = 0; glitchrunkludge=false; } if (statedelay <= 0) { switch(state) { case 0: //Do nothing here! Standard game state if (script.running) { if (pausescript && !advancetext) { /* Prevent softlocks if we somehow don't have advancetext */ pausescript = false; } } else { if (completestop) { /* Close potential collection dialogue if warping to ship */ graphics.textboxremove(); graphics.showcutscenebars = false; } /* Prevent softlocks if there's no cutscene running right now */ hascontrol = true; completestop = false; } break; case 1: //Game initilisation setstate(0); break; case 2: //Opening cutscene advancetext = true; hascontrol = false; setstate(3); graphics.createtextbox("To do: write quick", 50, 80, TEXT_COLOUR("cyan")); graphics.addline("intro to story!"); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); //Oh no! what happen to rest of crew etc crash into dimension break; case 4: //End of opening cutscene for now graphics.createtextbox("", -1, 195, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtimer(60); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, wasd_textbox); graphics.textboxapplyposition(); setstate(0); break; case 5: //Demo over advancetext = true; hascontrol = false; startscript = true; newscript="returntohub"; obj.removetrigger(5); setstate(6); break; case 7: //End of opening cutscene for now graphics.textboxremove(); hascontrol = true; advancetext = false; setstate(0); break; case 8: //Enter dialogue obj.removetrigger(8); if (!obj.flags[13]) { obj.flags[13] = true; graphics.createtextbox("", -1, 155, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtimer(60); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, map_textbox); graphics.textboxapplyposition(); } setstate(0); break; case 9: if (!map.custommode && nocompetitive()) { returntolab(); startscript = true; newscript = "disableaccessibility"; setstate(0); break; } //Start SWN Minigame Mode B obj.removetrigger(9); swnmode = true; swngame = SWN_START_SUPERGRAVITRON_STEP_1; swndelay = 150; swntimer = 60 * 30; //set the checkpoint in the middle of the screen savepoint = 0; savex = 148; savey = 100; savegc = 0; saverx = roomx; savery = roomy; savedir = 0; setstate(0); break; case 10: //Start SWN Minigame Mode A obj.removetrigger(10); swnmode = true; swngame = SWN_START_GRAVITRON_STEP_1; swndelay = 150; swntimer = 60 * 30; //set the checkpoint in the middle of the screen savepoint = 0; savex = 148; savey = 100; savegc = 0; saverx = roomx; savery = roomy; savedir = 0; setstate(0); break; case 11: graphics.textboxremovefast(); graphics.createtextbox("", -1, 3, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtimer(180); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, im1_instructions_textbox2); graphics.textboxapplyposition(); setstate(0); break; case 12: obj.removetrigger(12); if (!obj.flags[61]) { obj.flags[61] = true; graphics.textboxremovefast(); graphics.createtextbox("", -1, 3, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtimer(120); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, im1_instructions_textbox3); graphics.textboxapplyposition(); } setstate(0); break; case 13: //textbox removal obj.removetrigger(13); graphics.textboxremovefast(); setstate(0); break; case 14: graphics.createtextbox("", -1, 3, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtimer(280); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, im1_instructions_textbox1); graphics.textboxapplyposition(); setstate(0); break; case 15: { //leaving the naughty corner int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[obj.getplayer()].tile = 0; } setstate(0); break; } case 16: { //entering the naughty corner int i = obj.getplayer(); if(INBOUNDS_VEC(i, obj.entities) && obj.entities[i].tile == 0) { obj.entities[i].tile = 144; music.playef(Sound_CRY); } setstate(0); break; } case 17: //Arrow key tutorial obj.removetrigger(17); if (BUTTONGLYPHS_keyboard_is_active()) { graphics.createtextbox("", -1, 187, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtimer(100); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, arrowkey_textbox); graphics.textboxapplyposition(); } setstate(0); break; case 20: if (!obj.flags[1]) { obj.flags[1] = true; setstate(0); graphics.textboxremove(); } obj.removetrigger(20); break; case 21: if (!obj.flags[2]) { obj.flags[2] = true; setstate(0); graphics.textboxremove(); } obj.removetrigger(21); break; case 22: if (!obj.flags[3]) { graphics.textboxremovefast(); obj.flags[3] = true; setstate(0); graphics.createtextbox("", -1, 25, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtimer(60); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, flip_textbox); graphics.textboxapplyposition(); } obj.removetrigger(22); break; case 30: //Generic "run script" if (!obj.flags[4]) { obj.flags[4] = true; startscript = true; newscript="firststeps"; setstate(0); } obj.removetrigger(30); setstate(0); break; case 31: //state = 55; setstatedelay(50); setstate(0); setstatedelay(0); if (!obj.flags[6]) { obj.flags[6] = true; obj.flags[5] = true; startscript = true; newscript="communicationstation"; setstate(0); setstatedelay(0); } obj.removetrigger(31); break; case 32: //Generic "run script" if (!obj.flags[7]) { obj.flags[7] = true; startscript = true; newscript="teleporterback"; setstate(0); } obj.removetrigger(32); setstate(0); break; case 33: //Generic "run script" if (!obj.flags[9]) { obj.flags[9] = true; startscript = true; newscript="rescueblue"; setstate(0); } obj.removetrigger(33); setstate(0); break; case 34: //Generic "run script" if (!obj.flags[10]) { obj.flags[10] = true; startscript = true; newscript="rescueyellow"; setstate(0); } obj.removetrigger(34); setstate(0); break; case 35: //Generic "run script" if (!obj.flags[11]) { obj.flags[11] = true; startscript = true; newscript="rescuegreen"; setstate(0); } obj.removetrigger(35); setstate(0); break; case 36: //Generic "run script" if (!obj.flags[8]) { obj.flags[8] = true; startscript = true; newscript="rescuered"; setstate(0); } obj.removetrigger(36); setstate(0); break; case 37: //Generic "run script" if (companion == 0) { startscript = true; newscript="int2_yellow"; setstate(0); } obj.removetrigger(37); setstate(0); break; case 38: //Generic "run script" if (companion == 0) { startscript = true; newscript="int2_red"; setstate(0); } obj.removetrigger(38); setstate(0); break; case 39: //Generic "run script" if (companion == 0) { startscript = true; newscript="int2_green"; setstate(0); } obj.removetrigger(39); setstate(0); break; case 40: //Generic "run script" if (companion == 0) { startscript = true; newscript="int2_blue"; setstate(0); } obj.removetrigger(40); setstate(0); break; case 41: //Generic "run script" if (!obj.flags[60]) { obj.flags[60] = true; startscript = true; if (lastsaved == 2) { newscript = "int1yellow_2"; } else if (lastsaved == 3) { newscript = "int1red_2"; } else if (lastsaved == 4) { newscript = "int1green_2"; } else if (lastsaved == 5) { newscript = "int1blue_2"; } setstate(0); } obj.removetrigger(41); setstate(0); break; case 42: //Generic "run script" if (!obj.flags[62]) { obj.flags[62] = true; startscript = true; if (lastsaved == 2) { newscript = "int1yellow_3"; } else if (lastsaved == 3) { newscript = "int1red_3"; } else if (lastsaved == 4) { newscript = "int1green_3"; } else if (lastsaved == 5) { newscript = "int1blue_3"; } setstate(0); } obj.removetrigger(42); setstate(0); break; case 43: //Generic "run script" if (!obj.flags[63]) { obj.flags[63] = true; startscript = true; if (lastsaved == 2) { newscript = "int1yellow_4"; } else if (lastsaved == 3) { newscript = "int1red_4"; } else if (lastsaved == 4) { newscript = "int1green_4"; } else if (lastsaved == 5) { newscript = "int1blue_4"; } setstate(0); } obj.removetrigger(43); setstate(0); break; case 44: //Generic "run script" if (!obj.flags[64]) { obj.flags[64] = true; startscript = true; if (lastsaved == 2) { newscript = "int1yellow_5"; } else if (lastsaved == 3) { newscript = "int1red_5"; } else if (lastsaved == 4) { newscript = "int1green_5"; } else if (lastsaved == 5) { newscript = "int1blue_5"; } setstate(0); } obj.removetrigger(44); setstate(0); break; case 45: //Generic "run script" if (!obj.flags[65]) { obj.flags[65] = true; startscript = true; if (lastsaved == 2) { newscript = "int1yellow_6"; } else if (lastsaved == 3) { newscript = "int1red_6"; } else if (lastsaved == 4) { newscript = "int1green_6"; } else if (lastsaved == 5) { newscript = "int1blue_6"; } setstate(0); } obj.removetrigger(45); setstate(0); break; case 46: //Generic "run script" if (!obj.flags[66]) { obj.flags[66] = true; startscript = true; if (lastsaved == 2) { newscript = "int1yellow_7"; } else if (lastsaved == 3) { newscript = "int1red_7"; } else if (lastsaved == 4) { newscript = "int1green_7"; } else if (lastsaved == 5) { newscript = "int1blue_7"; } setstate(0); } obj.removetrigger(46); setstate(0); break; case 47: //Generic "run script" if (!obj.flags[69]) { obj.flags[69] = true; startscript = true; newscript="trenchwarfare"; setstate(0); } obj.removetrigger(47); setstate(0); break; case 48: //Generic "run script" if (!obj.flags[70]) { obj.flags[70] = true; startscript = true; newscript="trinketcollector"; setstate(0); } obj.removetrigger(48); setstate(0); break; case 49: //Start final level music if (!obj.flags[71]) { obj.flags[71] = true; music.niceplay(Music_PREDESTINEDFATEREMIX); setstate(0); } obj.removetrigger(49); setstate(0); break; case 50: music.playef(Sound_VIOLET); graphics.createtextbox("", 5, 8, TEXT_COLOUR("purple")); graphics.textboxcommsrelay("Help! Can anyone hear this message?"); graphics.textboxtimer(60); incstate(); setstatedelay(100); break; case 51: music.playef(Sound_VIOLET); graphics.createtextbox("", 5, 8, TEXT_COLOUR("purple")); graphics.textboxcommsrelay("Verdigris? Are you out there? Are you ok?"); graphics.textboxtimer(60); incstate(); setstatedelay(100); break; case 52: music.playef(Sound_VIOLET); graphics.createtextbox("", 5, 8, TEXT_COLOUR("purple")); graphics.textboxcommsrelay("Please help us! We've crashed and need assistance!"); graphics.textboxtimer(60); incstate(); setstatedelay(100); break; case 53: music.playef(Sound_VIOLET); graphics.createtextbox("", 5, 8, TEXT_COLOUR("purple")); graphics.textboxcommsrelay("Hello? Anyone out there?"); graphics.textboxtimer(60); incstate(); setstatedelay(100); break; case 54: music.playef(Sound_VIOLET); graphics.createtextbox("", 5, 8, TEXT_COLOUR("purple")); graphics.textboxcommsrelay("This is Doctor Violet from the D.S.S. Souleye! Please respond!"); graphics.textboxtimer(60); incstate(); setstatedelay(100); break; case 55: music.playef(Sound_VIOLET); graphics.createtextbox("", 5, 8, TEXT_COLOUR("purple")); graphics.textboxcommsrelay("Please... Anyone..."); graphics.textboxtimer(60); incstate(); setstatedelay(100); break; case 56: music.playef(Sound_VIOLET); graphics.createtextbox("", 5, 8, TEXT_COLOUR("purple")); graphics.textboxcommsrelay("Please be alright, everyone..."); graphics.textboxtimer(60); setstate(50); setstatedelay(100); break; case 80: //Used to return to menu from the game if (graphics.fademode == FADE_FULLY_BLACK) { incstate(); } break; case 81: quittomenu(); music.play(Music_PRESENTINGVVVVVV); //should be after quittomenu() setstate(0); break; case 82: //Time Trial Complete! obj.removetrigger(82); if (map.custommode && !map.custommodeforreal) { returntoeditor(); ed.show_note(loc::gettext("Time trial completed")); break; } if (translator_exploring) { translator_exploring_allowtele = true; setstate(0); break; } hascontrol = false; if (timetrialcheater) { SDL_zeroa(obj.collect); } timetrialresulttime = help.hms_to_seconds(hours, minutes, seconds); timetrialresultframes = frames; timetrialresulttrinkets = trinkets(); timetrialresultshinytarget = timetrialshinytarget; timetrialresultpar = timetrialpar; timetrialresultdeaths = deathcounts; timetrialrank = 0; if (timetrialresulttime <= timetrialpar) timetrialrank++; if (trinkets() >= timetrialshinytarget) timetrialrank++; if (deathcounts == 0) timetrialrank++; if (timetrialresulttime < besttimes[timetriallevel] || (timetrialresulttime == besttimes[timetriallevel] && timetrialresultframes < bestframes[timetriallevel]) || besttimes[timetriallevel]==-1) { besttimes[timetriallevel] = timetrialresulttime; bestframes[timetriallevel] = timetrialresultframes; } if (timetrialresulttrinkets > besttrinkets[timetriallevel] || besttrinkets[timetriallevel]==-1) { besttrinkets[timetriallevel] = trinkets(); } if (deathcounts < bestlives[timetriallevel] || bestlives[timetriallevel]==-1) { bestlives[timetriallevel] = deathcounts; } if (timetrialrank > bestrank[timetriallevel] || bestrank[timetriallevel]==-1) { bestrank[timetriallevel] = timetrialrank; if (timetrialrank >= 3) { switch (timetriallevel) { case TimeTrial_SPACESTATION1: unlockAchievement("vvvvvvtimetrial_station1_fixed"); break; case TimeTrial_LABORATORY: unlockAchievement("vvvvvvtimetrial_lab_fixed"); break; case TimeTrial_TOWER: unlockAchievement("vvvvvvtimetrial_tower_fixed"); break; case TimeTrial_SPACESTATION2: unlockAchievement("vvvvvvtimetrial_station2_fixed"); break; case TimeTrial_WARPZONE: unlockAchievement("vvvvvvtimetrial_warp_fixed"); break; case TimeTrial_FINALLEVEL: unlockAchievement("vvvvvvtimetrial_final_fixed"); } } } savestatsandsettings(); graphics.fademode = FADE_START_FADEOUT; music.fadeout(); incstate(); break; case 83: frames--; if (graphics.fademode == FADE_FULLY_BLACK) { incstate(); } break; case 84: quittomenu(); createmenu(Menu::timetrialcomplete); setstate(0); break; case 85: //Cutscene skip version of final level change obj.removetrigger(85); //Init final stretch incstate(); music.playef(Sound_FLASH); music.play(Music_POSITIVEFORCE); obj.flags[72] = true; screenshake = 10; flashlight = 5; map.finalstretch = true; map.warpx = false; map.warpy = false; map.background = 6; map.final_colormode = true; map.final_colorframe = 1; setstate(0); break; //From 90-100 are run scripts for the eurogamer expo only, remove later case 90: //Generic "run script" startscript = true; newscript="startexpolevel_station1"; obj.removetrigger(90); setstate(0); break; case 91: //Generic "run script" startscript = true; newscript="startexpolevel_lab"; obj.removetrigger(91); setstate(0); break; case 92: //Generic "run script" startscript = true; newscript="startexpolevel_warp"; obj.removetrigger(92); setstate(0); break; case 93: //Generic "run script" startscript = true; newscript="startexpolevel_tower"; obj.removetrigger(93); setstate(0); break; case 94: //Generic "run script" startscript = true; newscript="startexpolevel_station2"; obj.removetrigger(94); setstate(0); break; case 95: //Generic "run script" startscript = true; newscript="startexpolevel_final"; obj.removetrigger(95); setstate(0); break; case 96: //Used to return to gravitron to game if (graphics.fademode == FADE_FULLY_BLACK) { incstate(); } break; case 97: returntolab(); setstate(0); break; case 100: // // Meeting crewmate in the warpzone // obj.removetrigger(100); if (!obj.flags[4]) { obj.flags[4] = true; incstate(); } break; case 101: { int i = obj.getplayer(); hascontrol = false; if (INBOUNDS_VEC(i, obj.entities) && obj.entities[i].onroof > 0 && gravitycontrol == 1) { gravitycontrol = 0; music.playef(Sound_UNFLIP); } if (INBOUNDS_VEC(i, obj.entities) && obj.entities[i].onground > 0) { incstate(); } } break; case 102: { companion = 6; int i = obj.getcompanion(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].tile = 0; obj.entities[i].state = 1; } advancetext = true; hascontrol = false; graphics.createtextbox("Captain! I've been so worried!", 60, 90, 164, 255, 164); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_VERDIGRIS); } break; case 104: graphics.createtextbox("I'm glad you're ok!", 135, 152, TEXT_COLOUR("cyan")); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_VIRIDIAN); graphics.textboxactive(); break; case 106: { graphics.createtextbox("I've been trying to find a", 74, 70, 164, 255, 164); graphics.addline("way out, but I keep going"); graphics.addline("around in circles..."); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_CRY); graphics.textboxactive(); int i = obj.getcompanion(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].tile = 54; obj.entities[i].state = 0; } } break; case 108: graphics.createtextbox("Don't worry! I have a", 125, 152, TEXT_COLOUR("cyan")); graphics.addline("teleporter key!"); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_VIRIDIAN); graphics.textboxactive(); break; case 110: { int i = obj.getcompanion(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].tile = 0; obj.entities[i].state = 1; } graphics.createtextbox("Follow me!", 185, 154, TEXT_COLOUR("cyan")); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_VIRIDIAN); graphics.textboxactive(); } break; case 112: graphics.textboxremove(); hascontrol = true; advancetext = false; setstate(0); break; case 115: // // Test script for space station, totally delete me! // hascontrol = false; incstate(); break; case 116: advancetext = true; hascontrol = false; graphics.createtextbox("Sorry Eurogamers! Teleporting around", 60 - 20, 200, 255, 64, 64); graphics.addline("the map doesn't work in this version!"); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); graphics.textboxcenterx(); incstate(); break; case 118: graphics.textboxremove(); hascontrol = true; advancetext = false; setstate(0); break; case 120: // // Meeting crewmate in the space station // obj.removetrigger(120); if (!obj.flags[5]) { obj.flags[5] = true; incstate(); } break; case 121: { int i = obj.getplayer(); hascontrol = false; if (INBOUNDS_VEC(i, obj.entities) && obj.entities[i].onground > 0 && gravitycontrol == 0) { gravitycontrol = 1; music.playef(Sound_UNFLIP); } if (INBOUNDS_VEC(i, obj.entities) && obj.entities[i].onroof > 0) { incstate(); } } break; case 122: { companion = 7; int i = obj.getcompanion(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].tile = 6; obj.entities[i].state = 1; } advancetext = true; hascontrol = false; graphics.createtextbox("Captain! You're ok!", 60-10, 90-40, TEXT_COLOUR("yellow")); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_VITELLARY); break; } case 124: { graphics.createtextbox("I've found a teleporter, but", 60-20, 90 - 40, TEXT_COLOUR("yellow")); graphics.addline("I can't get it to go anywhere..."); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_CRY); graphics.textboxactive(); break; } case 126: graphics.createtextbox("I can help with that!", 125, 152-40, TEXT_COLOUR("cyan")); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_VIRIDIAN); graphics.textboxactive(); break; case 128: graphics.createtextbox("I have the teleporter", 130, 152-35, TEXT_COLOUR("cyan")); graphics.addline("codex for our ship!"); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_VIRIDIAN); graphics.textboxactive(); break; case 130: { graphics.createtextbox("Yey! Let's go home!", 60-30, 90-35, TEXT_COLOUR("yellow")); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_VITELLARY); graphics.textboxactive(); int i = obj.getcompanion(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].tile = 6; obj.entities[i].state = 1; } break; } case 132: graphics.textboxremove(); hascontrol = true; advancetext = false; setstate(0); break; case 200: //Init final stretch incstate(); music.playef(Sound_FLASH); obj.flags[72] = true; screenshake = 10; flashlight = 5; map.finalstretch = true; map.warpx = false; map.warpy = false; map.background = 6; map.final_colormode = true; map.final_colorframe = 1; startscript = true; newscript="finalterminal_finish"; setstate(0); break; // WARNING: If updating this code, make sure to update Map.cpp mapclass::twoframedelayfix() case 300: case 301: case 302: case 303: case 304: case 305: case 306: case 307: case 308: case 309: case 310: case 311: case 312: case 313: case 314: case 315: case 316: case 317: case 318: case 319: case 320: case 321: case 322: case 323: case 324: case 325: case 326: case 327: case 328: case 329: case 330: case 331: case 332: case 333: case 334: case 335: case 336: startscript = true; newscript="custom_"+customscript[state - 300]; obj.removetrigger(state); setstate(0); break; case 1000: graphics.showcutscenebars = true; hascontrol = false; completestop = true; incstate(); setstatedelay(15); break; case 1001: //Found a trinket! advancetext = true; incstate(); graphics.createtextboxflipme("", 50, 85, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, foundtrinket_textbox1); graphics.textboxapplyposition(); graphics.createtextboxflipme("", 50, 95, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxindex(graphics.textboxes.size() - 2); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, foundtrinket_textbox2); graphics.textboxapplyposition(); break; case 1002: if (!advancetext) { // Prevent softlocks if we somehow don't have advancetext incstate(); } break; case 1003: graphics.textboxremove(); hascontrol = true; advancetext = false; completestop = false; setstate(0); if (music.currentsong > -1) { music.fadeMusicVolumeIn(3000); } graphics.showcutscenebars = false; break; case 1010: graphics.showcutscenebars = true; hascontrol = false; completestop = true; incstate(); setstatedelay(15); break; case 1011: //Found a crewmate! advancetext = true; incstate(); graphics.createtextboxflipme("", 50, 85, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, foundcrewmate_textbox1); graphics.textboxapplyposition(); graphics.createtextboxflipme("", 50, 95, TEXT_COLOUR("gray")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxindex(graphics.textboxes.size() - 2); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, foundcrewmate_textbox2); graphics.textboxapplyposition(); break; case 1012: if (!advancetext) { // Prevent softlocks if we somehow don't have advancetext incstate(); } break; case 1013: graphics.textboxremove(); hascontrol = true; advancetext = false; completestop = false; setstate(0); if(cl.numcrewmates()-crewmates()==0) { if(map.custommodeforreal) { graphics.fademode = FADE_START_FADEOUT; setstate(1014); } else { returntoeditor(); ed.show_note(loc::gettext("Level completed")); } } else { if (cl.levmusic > 0) { music.fadeMusicVolumeIn(3000); } } graphics.showcutscenebars = false; break; case 1014: frames--; if (graphics.fademode == FADE_FULLY_BLACK) { incstate(); } break; case 1015: //Update level stats /* FIXME: Have to add check to not save stats for the dumb hack * `special/stdin.vvvvvv` filename... see elsewhere, grep for * `special/stdin`! */ if(cl.numcrewmates()-crewmates()==0 && customlevelfilename != "levels/special/stdin.vvvvvv") { //Finished level if (trinkets() >= cl.numtrinkets()) { //and got all the trinkets! updatecustomlevelstats(customlevelfilename, 3); } else { updatecustomlevelstats(customlevelfilename, 1); } } quittomenu(); music.play(Music_PRESENTINGVVVVVV); //should be after quittomenu() setstate(0); break; case 2000: //Game Saved! savetele_textbox(); setstate(0); break; case 2500: music.play(Music_PAUSE); //Activating a teleporter (appear) incstate(); setstatedelay(15); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; case 2501: //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; //we're done here! music.playef(Sound_TELEPORT); break; case 2502: { //Activating a teleporter 2 incstate(); setstatedelay(5); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].colour = 0; obj.entities[i].invis = false; int j = obj.getteleporter(); if (INBOUNDS_VEC(j, obj.entities)) { obj.entities[i].xp = obj.entities[j].xp+44; obj.entities[i].yp = obj.entities[j].yp+44; obj.entities[i].lerpoldxp = obj.entities[i].xp; obj.entities[i].lerpoldyp = obj.entities[i].yp; } obj.entities[i].ay = -6; obj.entities[i].ax = 6; obj.entities[i].vy = -6; obj.entities[i].vx = 6; } i = obj.getteleporter(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].tile = 1; obj.entities[i].colour = 101; } break; } case 2503: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 10; } break; } case 2504: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { //obj.entities[i].xp += 10; } break; } case 2505: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 8; } break; } case 2506: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 6; } break; } case 2507: { incstate(); break; } case 2508: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 2; } break; } case 2509: { incstate(); setstatedelay(15); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 1; } break; } case 2510: advancetext = true; hascontrol = false; graphics.createtextbox("Hello?", 125+24, 152-20, TEXT_COLOUR("cyan")); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_VIRIDIAN); graphics.textboxactive(); break; case 2512: advancetext = true; hascontrol = false; graphics.createtextbox("Is anyone there?", 125+8, 152-24, TEXT_COLOUR("cyan")); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); incstate(); music.playef(Sound_VIRIDIAN); graphics.textboxactive(); break; case 2514: graphics.textboxremove(); hascontrol = true; advancetext = false; setstate(0); music.play(Music_POTENTIALFORANYTHING); break; case 3000: //Activating a teleporter (long version for level complete) incstate(); setstatedelay(30); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; case 3001: //Activating a teleporter 2 incstate(); setstatedelay(15); flashlight = 5; music.playef(Sound_FLASH); break; case 3002: //Activating a teleporter 2 incstate(); setstatedelay(15); flashlight = 5; music.playef(Sound_FLASH); break; case 3003: //Activating a teleporter 2 incstate(); setstatedelay(15); flashlight = 5; music.playef(Sound_FLASH); break; case 3004: //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; //we're done here! music.playef(Sound_TELEPORT); break; case 3005: { //Activating a teleporter 2 incstate(); setstatedelay(50); switch(companion) { case 6: setstate(3006); break; //Warp Zone case 7: setstate(3020); break; //Space Station case 8: setstate(3040); break; //Lab case 9: setstate(3060); break; //Tower case 10: setstate(3080); break; //Intermission 2 case 11: setstate(3085); break; //Intermission 1 } if (translator_exploring_allowtele) { setstate(3090); } int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].colour = 0; obj.entities[i].invis = true; } i = obj.getcompanion(); if(INBOUNDS_VEC(i, obj.entities)) { obj.disableentity(i); } i = obj.getteleporter(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].tile = 1; obj.entities[i].colour = 100; } break; } case 3006: //Level complete! (warp zone) unlocknum(Unlock_WARPZONE_COMPLETE); lastsaved = 4; music.play(Music_PATHCOMPLETE); incstate(); setstatedelay(75); levelcomplete_textbox(); break; case 3007: incstate(); setstatedelay(45); crewmate_textbox(13); break; case 3008: incstate(); setstatedelay(45); remaining_textbox(); break; case 3009: incstate(); setstatedelay(0); actionprompt_textbox(); break; case 3010: if (jumppressed) { incstate(); setstatedelay(30); graphics.textboxremove(); } break; case 3011: setstate(3070); setstatedelay(0); break; case 3020: //Level complete! (Space Station 2) unlocknum(Unlock_SPACESTATION2_COMPLETE); lastsaved = 2; music.play(Music_PATHCOMPLETE); incstate(); setstatedelay(75); levelcomplete_textbox(); break; case 3021: incstate(); setstatedelay(45); crewmate_textbox(14); break; case 3022: incstate(); setstatedelay(45); remaining_textbox(); break; case 3023: incstate(); setstatedelay(0); actionprompt_textbox(); break; case 3024: if (jumppressed) { incstate(); setstatedelay(30); graphics.textboxremove(); } break; case 3025: setstate(3070); setstatedelay(0); break; case 3040: //Level complete! (Lab) unlocknum(Unlock_LABORATORY_COMPLETE); lastsaved = 5; music.play(Music_PATHCOMPLETE); incstate(); setstatedelay(75); levelcomplete_textbox(); break; case 3041: incstate(); setstatedelay(45); crewmate_textbox(16); break; case 3042: incstate(); setstatedelay(45); remaining_textbox(); break; case 3043: incstate(); setstatedelay(0); actionprompt_textbox(); break; case 3044: if (jumppressed) { incstate(); setstatedelay(30); graphics.textboxremove(); } break; case 3045: setstate(3070); setstatedelay(0); break; case 3050: //Level complete! (Space Station 1) unlocknum(Unlock_SPACESTATION1_COMPLETE); lastsaved = 1; music.play(Music_PATHCOMPLETE); incstate(); setstatedelay(75); levelcomplete_textbox(); break; case 3051: incstate(); setstatedelay(45); crewmate_textbox(20); break; case 3052: incstate(); setstatedelay(45); remaining_textbox(); break; case 3053: incstate(); setstatedelay(0); actionprompt_textbox(); break; case 3054: if (jumppressed) { incstate(); setstatedelay(30); graphics.textboxremove(); teleportscript = ""; } break; case 3055: graphics.fademode = FADE_START_FADEOUT; incstate(); setstatedelay(10); break; case 3056: if (graphics.fademode == FADE_FULLY_BLACK) { startscript = true; if (crewrescued() == 6) { newscript = "startlevel_final"; } else { if (nocutscenes) { newscript="bigopenworldskip"; } else { newscript = "bigopenworld"; } } setstate(0); } break; case 3060: //Level complete! (Tower) unlocknum(Unlock_TOWER_COMPLETE); lastsaved = 3; music.play(Music_PATHCOMPLETE); incstate(); setstatedelay(75); levelcomplete_textbox(); break; case 3061: incstate(); setstatedelay(45); crewmate_textbox(15); break; case 3062: incstate(); setstatedelay(45); remaining_textbox(); break; case 3063: incstate(); setstatedelay(0); actionprompt_textbox(); break; case 3064: if (jumppressed) { incstate(); setstatedelay(30); graphics.textboxremove(); } break; case 3065: setstate(3070); setstatedelay(0); break; case 3070: graphics.fademode = FADE_START_FADEOUT; incstate(); break; case 3071: if (graphics.fademode == FADE_FULLY_BLACK) { incstate(); } break; case 3072: //Ok, we need to adjust some flags based on who've we've rescued. Some of there conversation options //change depending on when they get back to the ship. if (lastsaved == 2) { if (crewstats[3]) obj.flags[25] = true; if (crewstats[4]) obj.flags[26] = true; if (crewstats[5]) obj.flags[24] = true; } else if (lastsaved == 3) { if (crewstats[2]) obj.flags[50] = true; if (crewstats[4]) obj.flags[49] = true; if (crewstats[5]) obj.flags[48] = true; } else if (lastsaved == 4) { if (crewstats[2]) obj.flags[54] = true; if (crewstats[3]) obj.flags[55] = true; if (crewstats[5]) obj.flags[56] = true; } else if (lastsaved == 5) { if (crewstats[2]) obj.flags[37] = true; if (crewstats[3]) obj.flags[38] = true; if (crewstats[4]) obj.flags[39] = true; } //We're pitch black now, make a decision companion = 0; if (crewrescued() == 6) { startscript = true; newscript="startlevel_final"; setstate(0); } else if (crewrescued() == 4) { companion = 11; supercrewmate = true; scmprogress = 0; startscript = true; newscript = "intermission_1"; obj.flags[19] = true; if (lastsaved == 2) obj.flags[32] = true; if (lastsaved == 3) obj.flags[35] = true; if (lastsaved == 4) obj.flags[34] = true; if (lastsaved == 5) obj.flags[33] = true; setstate(0); } else if (crewrescued() == 5) { startscript = true; newscript = "intermission_2"; obj.flags[20] = true; if (lastsaved == 2) obj.flags[32] = true; if (lastsaved == 3) obj.flags[35] = true; if (lastsaved == 4) obj.flags[34] = true; if (lastsaved == 5) obj.flags[33] = true; setstate(0); } else { startscript = true; newscript="regularreturn"; setstate(0); } break; case 3080: //returning from an intermission, very like 3070 if (inintermission) { graphics.fademode = FADE_START_FADEOUT; companion = 0; setstate(3100); } else { unlocknum(Unlock_INTERMISSION2_COMPLETE); graphics.fademode = FADE_START_FADEOUT; companion = 0; incstate(); } break; case 3081: if (graphics.fademode == FADE_FULLY_BLACK) { incstate(); } break; case 3082: map.finalmode = false; startscript = true; newscript="regularreturn"; setstate(0); break; case 3085: //returning from an intermission, very like 3070 //return to menu from here if (inintermission) { companion = 0; supercrewmate = false; incstate(); graphics.fademode = FADE_START_FADEOUT; music.fadeout(); setstate(3100); } else { unlocknum(Unlock_INTERMISSION1_COMPLETE); graphics.fademode = FADE_START_FADEOUT; companion = 0; supercrewmate = false; incstate(); } break; case 3086: if (graphics.fademode == FADE_FULLY_BLACK) { incstate(); } break; case 3087: map.finalmode = false; startscript = true; newscript="regularreturn"; setstate(0); break; case 3090: /* Teleporting in translator_exploring should be just like * the intermission replays: simply return to the menu */ companion = 0; supercrewmate = false; graphics.fademode = FADE_START_FADEOUT; music.fadeout(); setstate(3100); break; case 3091: /* Different Final Level ending for translator_exploring */ music.fadeout(); incstate(); setstatedelay(60); break; case 3092: graphics.fademode = FADE_START_FADEOUT; setstate(3100); break; case 3100: if (graphics.fademode == FADE_FULLY_BLACK) { incstate(); } break; case 3101: quittomenu(); music.play(Music_PRESENTINGVVVVVV); //should be after quittomenu(); setstate(0); break; case 3500: music.fadeout(); incstate(); setstatedelay(120); break; case 3501: //Game complete! unlockAchievement("vvvvvvgamecomplete"); unlocknum(UnlockTrophy_GAME_COMPLETE); crewstats[0] = true; incstate(); setstatedelay(75); music.play(Music_PLENARY); graphics.createtextboxflipme("", -1, 12, TEXT_COLOUR("cyan")); graphics.addline(" "); graphics.addline(""); graphics.addline(""); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_8X8); graphics.textboxcenterx(); graphics.setimage(TEXTIMAGE_GAMECOMPLETE); graphics.setlinegap(0); graphics.textboxapplyposition(); break; case 3502: incstate(); setstatedelay(45+15); graphics.createtextboxflipme("", -1, 64, TEXT_COLOUR("transparent")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, gamecomplete_textbox2); graphics.textboxapplyposition(); saveframes = frames; saveseconds = help.hms_to_seconds(hours, minutes, seconds); break; case 3503: incstate(); setstatedelay(45); graphics.createtextboxflipme("", 170, 84, TEXT_COLOUR("transparent")); graphics.textboxprintflags(PR_FONT_INTERFACE | PR_RTL_XFLIP); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, gamecomplete_textbox3); graphics.textboxapplyposition(); graphics.createtextboxflipme("", 180, 84, TEXT_COLOUR("transparent")); graphics.textboxprintflags(PR_FONT_INTERFACE | PR_RTL_XFLIP); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, gamecomplete_textbox4); graphics.textboxapplyposition(); break; case 3504: incstate(); setstatedelay(45+15); graphics.createtextboxflipme("", 170, 96, TEXT_COLOUR("transparent")); graphics.textboxprintflags(PR_FONT_INTERFACE | PR_RTL_XFLIP); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, gamecomplete_textbox5); graphics.textboxapplyposition(); graphics.createtextboxflipme("", 180, 96, TEXT_COLOUR("transparent")); graphics.textboxprintflags(PR_FONT_INTERFACE | PR_RTL_XFLIP); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, gamecomplete_textbox6); graphics.textboxapplyposition(); break; case 3505: incstate(); setstatedelay(45); graphics.createtextboxflipme("", 170, 123, TEXT_COLOUR("transparent")); graphics.textboxprintflags(PR_FONT_INTERFACE | PR_RTL_XFLIP); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, gamecomplete_textbox7); graphics.textboxapplyposition(); graphics.createtextboxflipme(help.String(totalflips), 180, 123, TEXT_COLOUR("transparent")); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_INTERFACE | PR_RTL_XFLIP); break; case 3506: incstate(); setstatedelay(45+15); graphics.createtextboxflipme("", 170, 135, TEXT_COLOUR("transparent")); graphics.textboxprintflags(PR_FONT_INTERFACE | PR_RTL_XFLIP); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, gamecomplete_textbox9); graphics.textboxapplyposition(); graphics.createtextboxflipme(help.String(deathcounts), 180, 135, TEXT_COLOUR("transparent")); graphics.textboxoriginalcontextauto(); graphics.textboxprintflags(PR_FONT_INTERFACE | PR_RTL_XFLIP); break; case 3507: incstate(); setstatedelay(45+15); graphics.createtextboxflipme("", -1, 158, TEXT_COLOUR("transparent")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, gamecomplete_textbox11); graphics.textboxapplyposition(); graphics.createtextboxflipme("", -1, 170, TEXT_COLOUR("transparent")); graphics.textboxprintflags(PR_FONT_INTERFACE); graphics.textboxcenterx(); graphics.textboxtranslate(TEXTTRANSLATE_FUNCTION, gamecomplete_textbox12); graphics.textboxapplyposition(); break; case 3508: incstate(); setstatedelay(0); actionprompt_textbox(); break; case 3509: if (jumppressed) { incstate(); setstatedelay(30); graphics.textboxremove(); } break; case 3510: //Save stats and stuff here if (!obj.flags[73]) { //flip mode complete unlockAchievement("vvvvvvgamecompleteflip"); unlocknum(UnlockTrophy_FLIPMODE_COMPLETE); } #ifndef MAKEANDPLAY if (!map.custommode) { if (bestgamedeaths == -1) { bestgamedeaths = deathcounts; } else { if (deathcounts < bestgamedeaths) { bestgamedeaths = deathcounts; } } } #endif if (bestgamedeaths > -1) { if (bestgamedeaths <= 500) { unlockAchievement("vvvvvvcomplete500"); } if (bestgamedeaths <= 250) { unlockAchievement("vvvvvvcomplete250"); } if (bestgamedeaths <= 100) { unlockAchievement("vvvvvvcomplete100"); } if (bestgamedeaths <= 50) { unlockAchievement("vvvvvvcomplete50"); } } if (nodeathmode || nodeatheligible) { unlockAchievement("vvvvvvmaster"); //bloody hell unlocknum(UnlockTrophy_NODEATHMODE_COMPLETE); } if (nodeathmode) { setstate(3520); setstatedelay(0); } else { setstatedelay(120); incstate(); } savestatsandsettings(); break; case 3511: { //Activating a teleporter (long version for level complete) int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].colour = 102; } incstate(); setstatedelay(30); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; } case 3512: //Activating a teleporter 2 incstate(); setstatedelay(15); flashlight = 5; music.playef(Sound_FLASH); break; case 3513: //Activating a teleporter 2 incstate(); setstatedelay(15); flashlight = 5; music.playef(Sound_FLASH); break; case 3514: //Activating a teleporter 2 incstate(); setstatedelay(15); flashlight = 5; music.playef(Sound_FLASH); break; case 3515: { //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].colour = 0; obj.entities[i].invis = true; } //we're done here! music.playef(Sound_TELEPORT); setstatedelay(60); break; } case 3516: graphics.fademode = FADE_START_FADEOUT; incstate(); break; case 3517: if (graphics.fademode == FADE_FULLY_BLACK) { incstate(); setstatedelay(30); } break; case 3518: graphics.fademode = FADE_START_FADEIN; setstate(0); setstatedelay(30); map.finalmode = false; map.final_colormode = false; map.final_mapcol = 0; map.final_colorframe = 0; map.finalstretch = false; obj.flags[72] = false; graphics.setbars(320); teleport_to_new_area = true; teleportscript = "gamecomplete"; break; case 3520: //NO DEATH MODE COMPLETE JESUS hascontrol = false; crewstats[0] = true; graphics.fademode = FADE_START_FADEOUT; incstate(); break; case 3521: if (graphics.fademode == FADE_FULLY_BLACK) { incstate(); } break; case 3522: copyndmresults(); quittomenu(); createmenu(Menu::nodeathmodecomplete); setstate(0); break; case 4000: //Activating a teleporter (short version) state++; // Increment manually -- gamestate modification might be locked at this point statedelay = 10; flashlight = 5; screenshake = 10; music.playef(Sound_FLASH); break; case 4001: //Activating a teleporter 2 state++; statedelay = 0; flashlight = 5; screenshake = 0; //we're done here! music.playef(Sound_TELEPORT); break; case 4002: { //Activating a teleporter 2 state++; statedelay = 10; int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].colour = 0; obj.entities[i].invis = true; } i = obj.getteleporter(); if(INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].tile = 1; obj.entities[i].colour = 100; } break; } case 4003: state = 0; statedelay = 0; teleport_to_new_area = true; unlockstate(); break; case 4010: //Activating a teleporter (default appear) incstate(); setstatedelay(15); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; case 4011: //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; music.playef(Sound_TELEPORT); break; case 4012: { //Activating a teleporter 2 incstate(); setstatedelay(5); int i = obj.getplayer(); int j = obj.getteleporter(); if (INBOUNDS_VEC(i, obj.entities)) { if (INBOUNDS_VEC(j, obj.entities)) { obj.entities[i].xp = obj.entities[j].xp+44; obj.entities[i].yp = obj.entities[j].yp+44; obj.entities[i].lerpoldxp = obj.entities[i].xp; obj.entities[i].lerpoldyp = obj.entities[i].yp; obj.entities[j].tile = 2; obj.entities[j].colour = 101; } obj.entities[i].colour = 0; obj.entities[i].invis = false; obj.entities[i].dir = 1; obj.entities[i].ay = -6; obj.entities[i].ax = 6; obj.entities[i].vy = -6; obj.entities[i].vx = 6; } break; } case 4013: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 10; } break; } case 4014: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 10; } break; } case 4015: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 8; } break; } case 4016: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 6; } break; } case 4017: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 3; } break; } case 4018: { incstate(); setstatedelay(15); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 1; } break; } case 4019: { if (intimetrial || nodeathmode || inintermission) { } else { savetele(); } int i = obj.getteleporter(); activetele = true; if (INBOUNDS_VEC(i, obj.entities)) { teleblock.x = obj.entities[i].xp - 32; teleblock.y = obj.entities[i].yp - 32; } teleblock.w = 160; teleblock.h = 160; hascontrol = true; advancetext = false; setstate(0); break; } case 4020: //Activating a teleporter (default appear) incstate(); setstatedelay(15); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; case 4021: //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; music.playef(Sound_TELEPORT); break; case 4022: { //Activating a teleporter 2 incstate(); setstatedelay(5); int i = obj.getplayer(); int j = obj.getteleporter(); if (INBOUNDS_VEC(i, obj.entities)) { if (INBOUNDS_VEC(j, obj.entities)) { obj.entities[i].xp = obj.entities[j].xp+44; obj.entities[i].yp = obj.entities[j].yp+44; obj.entities[i].lerpoldxp = obj.entities[i].xp; obj.entities[i].lerpoldyp = obj.entities[i].yp; obj.entities[j].tile = 2; obj.entities[j].colour = 101; } obj.entities[i].colour = 0; obj.entities[i].invis = false; obj.entities[i].dir = 1; obj.entities[i].ay = -6; obj.entities[i].ax = 6; obj.entities[i].vy = -6; obj.entities[i].vx = 6; } break; } case 4023: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 12; } break; } case 4024: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 12; } break; } case 4025: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 10; } break; } case 4026: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 8; } break; } case 4027: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 5; } break; } case 4028: { incstate(); setstatedelay(15); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 2; } break; } case 4029: hascontrol = true; advancetext = false; setstate(0); break; case 4030: //Activating a teleporter (default appear) incstate(); setstatedelay(15); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; case 4031: //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; music.playef(Sound_TELEPORT); break; case 4032: { //Activating a teleporter 2 incstate(); setstatedelay(5); int i = obj.getplayer(); int j = obj.getteleporter(); if (INBOUNDS_VEC(i, obj.entities)) { if (INBOUNDS_VEC(j, obj.entities)) { obj.entities[i].xp = obj.entities[j].xp+44; obj.entities[i].yp = obj.entities[j].yp+44; obj.entities[i].lerpoldxp = obj.entities[i].xp; obj.entities[i].lerpoldyp = obj.entities[i].yp; obj.entities[j].tile = 2; obj.entities[j].colour = 101; } obj.entities[i].colour = 0; obj.entities[i].invis = false; obj.entities[i].dir = 0; obj.entities[i].ay = -6; obj.entities[i].ax = -6; obj.entities[i].vy = -6; obj.entities[i].vx = -6; } break; } case 4033: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 12; } break; } case 4034: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 12; } break; } case 4035: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 10; } break; } case 4036: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 8; } break; } case 4037: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 5; } break; } case 4038: { incstate(); setstatedelay(15); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 2; } break; } case 4039: hascontrol = true; advancetext = false; setstate(0); break; case 4040: //Activating a teleporter (default appear) incstate(); setstatedelay(15); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; case 4041: //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; music.playef(Sound_TELEPORT); break; case 4042: { //Activating a teleporter 2 incstate(); setstatedelay(5); int i = obj.getplayer(); int j = obj.getteleporter(); if (INBOUNDS_VEC(i, obj.entities)) { if (INBOUNDS_VEC(j, obj.entities)) { obj.entities[i].xp = obj.entities[j].xp+44; obj.entities[i].yp = obj.entities[j].yp+44; obj.entities[i].lerpoldxp = obj.entities[i].xp; obj.entities[i].lerpoldyp = obj.entities[i].yp; obj.entities[j].tile = 2; obj.entities[j].colour = 101; } obj.entities[i].colour = 0; obj.entities[i].invis = false; obj.entities[i].dir = 1; obj.entities[i].ay = -6; obj.entities[i].ax = 6; obj.entities[i].vy = -6; obj.entities[i].vx = 6; } break; } case 4043: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 12; obj.entities[i].yp -= 15; } break; } case 4044: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 12; obj.entities[i].yp -= 10; } break; } case 4045: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 12; obj.entities[i].yp -= 10; } break; } case 4046: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 8; obj.entities[i].yp -= 8; } break; } case 4047: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 6; obj.entities[i].yp -= 8; } break; } case 4048: { incstate(); setstatedelay(15); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 3; } break; } case 4049: hascontrol = true; advancetext = false; setstate(0); break; case 4050: //Activating a teleporter (default appear) incstate(); setstatedelay(15); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; case 4051: //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; music.playef(Sound_TELEPORT); break; case 4052: { //Activating a teleporter 2 incstate(); setstatedelay(5); int i = obj.getplayer(); int j = obj.getteleporter(); if (INBOUNDS_VEC(i, obj.entities)) { if (INBOUNDS_VEC(j, obj.entities)) { obj.entities[i].xp = obj.entities[j].xp+44; obj.entities[i].yp = obj.entities[j].yp+44; obj.entities[i].lerpoldxp = obj.entities[i].xp; obj.entities[i].lerpoldyp = obj.entities[i].yp; obj.entities[j].tile = 2; obj.entities[j].colour = 101; } obj.entities[i].colour = 0; obj.entities[i].invis = false; obj.entities[i].dir = 1; obj.entities[i].ay = -6; obj.entities[i].ax = 6; obj.entities[i].vy = -6; obj.entities[i].vx = 6; } break; } case 4053: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 4; obj.entities[i].yp -= 15; } break; } case 4054: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 4; obj.entities[i].yp -= 10; } break; } case 4055: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 4; obj.entities[i].yp -= 10; } break; } case 4056: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 4; obj.entities[i].yp -= 8; } break; } case 4057: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 2; obj.entities[i].yp -= 8; } break; } case 4058: { incstate(); setstatedelay(15); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 1; } break; } case 4059: hascontrol = true; advancetext = false; setstate(0); break; case 4060: //Activating a teleporter (default appear) incstate(); setstatedelay(15); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; case 4061: //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; music.playef(Sound_TELEPORT); break; case 4062: { //Activating a teleporter 2 incstate(); setstatedelay(5); int i = obj.getplayer(); int j = obj.getteleporter(); if (INBOUNDS_VEC(i, obj.entities)) { if (INBOUNDS_VEC(j, obj.entities)) { obj.entities[i].xp = obj.entities[j].xp+44; obj.entities[i].yp = obj.entities[j].yp+44; obj.entities[i].lerpoldxp = obj.entities[i].xp; obj.entities[i].lerpoldyp = obj.entities[i].yp; obj.entities[j].tile = 2; obj.entities[j].colour = 101; } obj.entities[i].colour = 0; obj.entities[i].invis = false; obj.entities[i].dir = 0; obj.entities[i].ay = -6; obj.entities[i].ax = -6; obj.entities[i].vy = -6; obj.entities[i].vx = -6; } break; } case 4063: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 28; obj.entities[i].yp -= 8; } break; } case 4064: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 28; obj.entities[i].yp -= 8; } break; } case 4065: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 25; } break; } case 4066: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 25; } break; } case 4067: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 20; } break; } case 4068: { incstate(); setstatedelay(15); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp -= 16; } break; } case 4069: hascontrol = true; advancetext = false; setstate(0); break; case 4070: //Activating a teleporter (special for final script, player has colour changed to match rescued crewmate) incstate(); setstatedelay(15); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; case 4071: //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; music.playef(Sound_TELEPORT); break; case 4072: { //Activating a teleporter 2 incstate(); setstatedelay(5); int i = obj.getplayer(); int j = obj.getteleporter(); if (INBOUNDS_VEC(i, obj.entities)) { if (INBOUNDS_VEC(j, obj.entities)) { obj.entities[i].xp = obj.entities[j].xp+44; obj.entities[i].yp = obj.entities[j].yp+44; obj.entities[i].lerpoldxp = obj.entities[i].xp; obj.entities[i].lerpoldyp = obj.entities[i].yp; obj.entities[j].tile = 2; obj.entities[j].colour = 101; } obj.entities[i].invis = false; obj.entities[i].dir = 1; obj.entities[i].colour = graphics.crewcolour(lastsaved); obj.entities[i].ay = -6; obj.entities[i].ax = 6; obj.entities[i].vy = -6; obj.entities[i].vx = 6; } break; } case 4073: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 10; } break; } case 4074: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 10; } break; } case 4075: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 8; } break; } case 4076: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 6; } break; } case 4077: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 3; } break; } case 4078: { incstate(); setstatedelay(15); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 1; } break; } case 4079: setstate(0); startscript = true; newscript = "finallevel_teleporter"; break; case 4080: //Activating a teleporter (default appear) incstate(); setstatedelay(15); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; case 4081: //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; music.playef(Sound_TELEPORT); break; case 4082: { //Activating a teleporter 2 incstate(); setstatedelay(5); int i = obj.getplayer(); int j = obj.getteleporter(); if (INBOUNDS_VEC(i, obj.entities)) { if (INBOUNDS_VEC(j, obj.entities)) { obj.entities[i].xp = obj.entities[j].xp+44; obj.entities[i].yp = obj.entities[j].yp+44; obj.entities[i].lerpoldxp = obj.entities[i].xp; obj.entities[i].lerpoldyp = obj.entities[i].yp; obj.entities[j].tile = 2; obj.entities[j].colour = 101; } obj.entities[i].colour = 0; obj.entities[i].invis = false; obj.entities[i].dir = 1; obj.entities[i].ay = -6; obj.entities[i].ax = 6; obj.entities[i].vy = -6; obj.entities[i].vx = 6; } break; } case 4083: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 10; } break; } case 4084: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 10; } break; } case 4085: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 8; } break; } case 4086: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 6; } break; } case 4087: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 3; } break; } case 4088: { incstate(); setstatedelay(15); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 1; } break; } case 4089: startscript = true; newscript = "gamecomplete_ending"; setstate(0); break; case 4090: //Activating a teleporter (default appear) incstate(); setstatedelay(15); flashlight = 5; screenshake = 90; music.playef(Sound_FLASH); break; case 4091: //Activating a teleporter 2 incstate(); setstatedelay(0); flashlight = 5; screenshake = 0; music.playef(Sound_TELEPORT); break; case 4092: { //Activating a teleporter 2 incstate(); setstatedelay(5); int i = obj.getplayer(); int j = obj.getteleporter(); if (INBOUNDS_VEC(i, obj.entities)) { if (INBOUNDS_VEC(j, obj.entities)) { obj.entities[i].xp = obj.entities[j].xp+44; obj.entities[i].yp = obj.entities[j].yp+44; obj.entities[i].lerpoldxp = obj.entities[i].xp; obj.entities[i].lerpoldyp = obj.entities[i].yp; obj.entities[j].tile = 2; obj.entities[j].colour = 101; } obj.entities[i].colour = 0; obj.entities[i].invis = false; obj.entities[i].dir = 1; obj.entities[i].ay = -6; obj.entities[i].ax = 6; obj.entities[i].vy = -6; obj.entities[i].vx = 6; } break; } case 4093: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 10; } break; } case 4094: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 10; } break; } case 4095: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 8; } break; } case 4096: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 6; } break; } case 4097: { incstate(); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 3; } break; } case 4098: { incstate(); setstatedelay(15); int i = obj.getplayer(); if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].xp += 1; } break; } case 4099: if (nocutscenes) { startscript = true; newscript = "levelonecompleteskip"; } else { startscript = true; newscript = "levelonecomplete_ending"; } setstate(0); break; } } } void Game::gethardestroom(void) { if (currentroomdeaths > hardestroomdeaths) { hardestroomdeaths = currentroomdeaths; hardestroom_x = roomx; hardestroom_y = roomy; hardestroom_finalstretch = map.finalstretch; if (map.roomname[0] == '\0') { hardestroom = map.hiddenname; hardestroom_specialname = true; } else { hardestroom = map.roomname; hardestroom_specialname = map.roomname_special; } } } void Game::deletestats(void) { if (!FILESYSTEM_delete("saves/unlock.vvv")) { vlog_error("Error deleting saves/unlock.vvv"); } else { for (int i = 0; i < numunlock; i++) { unlock[i] = false; unlocknotify[i] = false; } for (int i = 0; i < numtrials; i++) { besttimes[i] = -1; bestframes[i] = -1; besttrinkets[i] = -1; bestlives[i] = -1; bestrank[i] = -1; } swnrecord = 0; swnbestrank = 0; bestgamedeaths = -1; #ifndef MAKEANDPLAY graphics.setflipmode = false; #endif stat_trinkets = 0; } } void Game::deletesettings(void) { if (!FILESYSTEM_delete("saves/settings.vvv")) { vlog_error("Error deleting saves/settings.vvv"); } } void Game::unlocknum( int t ) { #ifdef MAKEANDPLAY UNUSED(t); #else if (map.custommode) { //Don't let custom levels unlock things! return; } unlock[t] = true; savestatsandsettings(); #endif } static bool stats_loaded = false; void Game::loadstats(struct ScreenSettings* screen_settings) { tinyxml2::XMLDocument doc; tinyxml2::XMLHandle hDoc(&doc); tinyxml2::XMLElement* pElem; tinyxml2::XMLElement* dataNode; stats_loaded = true; if (!FILESYSTEM_loadTiXml2Document("saves/unlock.vvv", doc)) { // Save unlock.vvv only. Maybe we have a settings.vvv laying around too, // and we don't want to overwrite that! savestats(screen_settings); return; } if (doc.Error()) { vlog_error("Error parsing unlock.vvv: %s", doc.ErrorStr()); return; } dataNode = hDoc .FirstChildElement() .FirstChildElement("Data") .FirstChildElement() .ToElement(); for (pElem = dataNode; pElem != NULL; pElem=pElem->NextSiblingElement()) { const char* pKey = pElem->Value(); const char* pText = pElem->GetText() ; if (pText == NULL) { pText = ""; } LOAD_ARRAY(unlock) LOAD_ARRAY(unlocknotify) LOAD_ARRAY(besttimes) LOAD_ARRAY(bestframes) LOAD_ARRAY(besttrinkets) LOAD_ARRAY(bestlives) LOAD_ARRAY(bestrank) if (SDL_strcmp(pKey, "bestgamedeaths") == 0) { bestgamedeaths = help.Int(pText); } if (SDL_strcmp(pKey, "stat_trinkets") == 0) { stat_trinkets = help.Int(pText); } if (SDL_strcmp(pKey, "swnbestrank") == 0) { swnbestrank = help.Int(pText); } if (SDL_strcmp(pKey, "swnrecord") == 0) { swnrecord = help.Int(pText); } } deserializesettings(dataNode, screen_settings); } void Game::deserializesettings(tinyxml2::XMLElement* dataNode, struct ScreenSettings* screen_settings) { // Don't duplicate controller buttons! controllerButton_flip.clear(); controllerButton_map.clear(); controllerButton_esc.clear(); controllerButton_restart.clear(); controllerButton_interact.clear(); for (tinyxml2::XMLElement* pElem = dataNode; pElem != NULL; pElem = pElem->NextSiblingElement()) { const char* pKey = pElem->Value(); const char* pText = pElem->GetText(); if (pText == NULL) { pText = ""; } if (SDL_strcmp(pKey, "fullscreen") == 0) { screen_settings->fullscreen = help.Int(pText); } if (SDL_strcmp(pKey, "stretch") == 0) { int mode = help.Int(pText); if (mode < 0 || mode >= NUM_SCALING_MODES) { /* Pick a sane default. */ mode = SCALING_INTEGER; } screen_settings->scalingMode = mode; } if (SDL_strcmp(pKey, "useLinearFilter") == 0) { screen_settings->linearFilter = help.Int(pText); } if (SDL_strcmp(pKey, "window_display") == 0) { screen_settings->windowDisplay = help.Int(pText); } if (SDL_strcmp(pKey, "window_width") == 0) { screen_settings->windowWidth = help.Int(pText); } if (SDL_strcmp(pKey, "window_height") == 0) { screen_settings->windowHeight = help.Int(pText); } if (SDL_strcmp(pKey, "noflashingmode") == 0) { noflashingmode = help.Int(pText); } if (SDL_strcmp(pKey, "colourblindmode") == 0) { colourblindmode = help.Int(pText); } if (SDL_strcmp(pKey, "setflipmode") == 0) { graphics.setflipmode = help.Int(pText); } if (SDL_strcmp(pKey, "invincibility") == 0) { map.invincibility = help.Int(pText); } if (SDL_strcmp(pKey, "slowdown") == 0) { slowdown = help.Int(pText); } if (SDL_strcmp(pKey, "advanced_smoothing") == 0) { screen_settings->badSignal = help.Int(pText); } if (SDL_strcmp(pKey, "usingmmmmmm") == 0) { music.usingmmmmmm = (bool) help.Int(pText); } if (SDL_strcmp(pKey, "ghostsenabled") == 0) { ghostsenabled = help.Int(pText); } if (SDL_strcmp(pKey, "skipfakeload") == 0) { skipfakeload = help.Int(pText); } if (SDL_strcmp(pKey, "disablepause") == 0) { disablepause = help.Int(pText); } if (SDL_strcmp(pKey, "disableaudiopause") == 0) { disableaudiopause = help.Int(pText); } if (SDL_strcmp(pKey, "over30mode") == 0) { over30mode = help.Int(pText); } if (SDL_strcmp(pKey, "inputdelay") == 0) { inputdelay = help.Int(pText); } if (SDL_strcmp(pKey, "glitchrunnermode") == 0) { GlitchrunnerMode_set(GlitchrunnerMode_string_to_enum(pText)); } if (SDL_strcmp(pKey, "showingametimer") == 0) { showingametimer = help.Int(pText); } if (SDL_strcmp(pKey, "vsync") == 0) { screen_settings->useVsync = help.Int(pText); } if (SDL_strcmp(pKey, "notextoutline") == 0) { graphics.notextoutline = help.Int(pText); } if (SDL_strcmp(pKey, "translucentroomname") == 0) { graphics.translucentroomname = help.Int(pText); } if (SDL_strcmp(pKey, "musicvolume") == 0) { music.user_music_volume = help.Int(pText); } if (SDL_strcmp(pKey, "soundvolume") == 0) { music.user_sound_volume = help.Int(pText); } if (SDL_strcmp(pKey, "separate_interact") == 0) { separate_interact = help.Int(pText); } if (SDL_strcmp(pKey, "flipButton") == 0) { SDL_GameControllerButton newButton; if (GetButtonFromString(pText, &newButton)) { controllerButton_flip.push_back(newButton); } } if (SDL_strcmp(pKey, "enterButton") == 0) { SDL_GameControllerButton newButton; if (GetButtonFromString(pText, &newButton)) { controllerButton_map.push_back(newButton); } } if (SDL_strcmp(pKey, "escButton") == 0) { SDL_GameControllerButton newButton; if (GetButtonFromString(pText, &newButton)) { controllerButton_esc.push_back(newButton); } } if (SDL_strcmp(pKey, "restartButton") == 0) { SDL_GameControllerButton newButton; if (GetButtonFromString(pText, &newButton)) { controllerButton_restart.push_back(newButton); } } if (SDL_strcmp(pKey, "interactButton") == 0) { SDL_GameControllerButton newButton; if (GetButtonFromString(pText, &newButton)) { controllerButton_interact.push_back(newButton); } } if (SDL_strcmp(pKey, "controllerSensitivity") == 0) { key.sensitivity = help.Int(pText); } if (SDL_strcmp(pKey, "lang") == 0) { loc::lang = std::string(pText); } if (SDL_strcmp(pKey, "lang_set") == 0) { loc::lang_set = help.Int(pText); } if (SDL_strcmp(pKey, "english_sprites") == 0) { loc::english_sprites = help.Int(pText); } if (SDL_strcmp(pKey, "new_level_font") == 0) { loc::new_level_font = std::string(pText); } if (SDL_strcmp(pKey, "roomname_translator") == 0 && loc::show_translator_menu) { roomname_translator::set_enabled(help.Int(pText)); } if (SDL_strcmp(pKey, "checkpoint_saving") == 0) { checkpoint_saving = help.Int(pText); } } setdefaultcontrollerbuttons(); } bool Game::savestats(bool sync /*= true*/) { struct ScreenSettings screen_settings; SDL_zero(screen_settings); gameScreen.GetSettings(&screen_settings); return savestats(&screen_settings, sync); } bool Game::savestats(const struct ScreenSettings* screen_settings, bool sync /*= true*/) { tinyxml2::XMLDocument doc; bool already_exists; if (!stats_loaded) { vlog_warn("Stats not loaded! Not writing unlock.vvv."); return false; } already_exists = FILESYSTEM_loadTiXml2Document("saves/unlock.vvv", doc); if (!already_exists) { vlog_info("No unlock.vvv found. Creating new file"); } else if (doc.Error()) { vlog_error("Error parsing existing unlock.vvv: %s", doc.ErrorStr()); vlog_info("Creating new unlock.vvv"); } xml::update_declaration(doc); tinyxml2::XMLElement * root = xml::update_element(doc, "Save"); xml::update_comment(root, " Save file " ); tinyxml2::XMLElement * dataNode = xml::update_element(root, "Data"); std::string s_unlock; for(size_t i = 0; i < SDL_arraysize(unlock); i++ ) { s_unlock += help.String(unlock[i]) + ","; } xml::update_tag(dataNode, "unlock", s_unlock.c_str()); std::string s_unlocknotify; for(size_t i = 0; i < SDL_arraysize(unlocknotify); i++ ) { s_unlocknotify += help.String(unlocknotify[i]) + ","; } xml::update_tag(dataNode, "unlocknotify", s_unlocknotify.c_str()); std::string s_besttimes; for(size_t i = 0; i < SDL_arraysize(besttimes); i++ ) { s_besttimes += help.String(besttimes[i]) + ","; } xml::update_tag(dataNode, "besttimes", s_besttimes.c_str()); std::string s_bestframes; for (size_t i = 0; i < SDL_arraysize(bestframes); i++) { s_bestframes += help.String(bestframes[i]) + ","; } xml::update_tag(dataNode, "bestframes", s_bestframes.c_str()); std::string s_besttrinkets; for(size_t i = 0; i < SDL_arraysize(besttrinkets); i++ ) { s_besttrinkets += help.String(besttrinkets[i]) + ","; } xml::update_tag(dataNode, "besttrinkets", s_besttrinkets.c_str()); std::string s_bestlives; for(size_t i = 0; i < SDL_arraysize(bestlives); i++ ) { s_bestlives += help.String(bestlives[i]) + ","; } xml::update_tag(dataNode, "bestlives", s_bestlives.c_str()); std::string s_bestrank; for(size_t i = 0; i < SDL_arraysize(bestrank); i++ ) { s_bestrank += help.String(bestrank[i]) + ","; } xml::update_tag(dataNode, "bestrank", s_bestrank.c_str()); xml::update_tag(dataNode, "bestgamedeaths", bestgamedeaths); xml::update_tag(dataNode, "stat_trinkets", stat_trinkets); xml::update_tag(dataNode, "swnbestrank", swnbestrank); xml::update_tag(dataNode, "swnrecord", swnrecord); serializesettings(dataNode, screen_settings); return FILESYSTEM_saveTiXml2Document("saves/unlock.vvv", doc, sync); } bool Game::savestatsandsettings(void) { const bool stats_saved = savestats(false); const bool settings_saved = savesettings(); return stats_saved && settings_saved; // Not the same as `savestats() && savesettings()`! } void Game::savestatsandsettings_menu(void) { // Call Game::savestatsandsettings(), but upon failure, go to the save error screen if (!savestatsandsettings() && !silence_settings_error) { createmenu(Menu::errorsavingsettings); map.nexttowercolour(); } } void Game::serializesettings(tinyxml2::XMLElement* dataNode, const struct ScreenSettings* screen_settings) { tinyxml2::XMLDocument& doc = xml::get_document(dataNode); xml::update_tag(dataNode, "fullscreen", (int) screen_settings->fullscreen); xml::update_tag(dataNode, "stretch", screen_settings->scalingMode); xml::update_tag(dataNode, "useLinearFilter", (int) screen_settings->linearFilter); xml::update_tag(dataNode, "window_display", screen_settings->windowDisplay); xml::update_tag(dataNode, "window_width", screen_settings->windowWidth); xml::update_tag(dataNode, "window_height", screen_settings->windowHeight); xml::update_tag(dataNode, "noflashingmode", noflashingmode); xml::update_tag(dataNode, "colourblindmode", colourblindmode); xml::update_tag(dataNode, "setflipmode", graphics.setflipmode); xml::update_tag(dataNode, "invincibility", map.invincibility); xml::update_tag(dataNode, "slowdown", slowdown); xml::update_tag(dataNode, "advanced_smoothing", (int) screen_settings->badSignal); xml::update_tag(dataNode, "usingmmmmmm", music.usingmmmmmm); xml::update_tag(dataNode, "ghostsenabled", (int) ghostsenabled); xml::update_tag(dataNode, "skipfakeload", (int) skipfakeload); xml::update_tag(dataNode, "disablepause", (int) disablepause); xml::update_tag(dataNode, "disableaudiopause", (int) disableaudiopause); xml::update_tag(dataNode, "notextoutline", (int) graphics.notextoutline); xml::update_tag(dataNode, "translucentroomname", (int) graphics.translucentroomname); xml::update_tag(dataNode, "over30mode", (int) over30mode); xml::update_tag(dataNode, "inputdelay", (int) inputdelay); xml::update_tag( dataNode, "glitchrunnermode", GlitchrunnerMode_enum_to_string(GlitchrunnerMode_get()) ); xml::update_tag(dataNode, "showingametimer", (int) showingametimer); xml::update_tag(dataNode, "vsync", (int) screen_settings->useVsync); xml::update_tag(dataNode, "musicvolume", music.user_music_volume); xml::update_tag(dataNode, "soundvolume", music.user_sound_volume); xml::update_tag(dataNode, "separate_interact", (int) separate_interact); // Delete all controller buttons we had previously. // dataNode->FirstChildElement() shouldn't be NULL at this point... // we've already added a bunch of elements for (tinyxml2::XMLElement* element = dataNode->FirstChildElement(); element != NULL; /* Increment code handled separately */) { const char* name = element->Name(); if (SDL_strcmp(name, "flipButton") == 0 || SDL_strcmp(name, "enterButton") == 0 || SDL_strcmp(name, "escButton") == 0 || SDL_strcmp(name, "restartButton") == 0 || SDL_strcmp(name, "interactButton") == 0) { // Can't just doc.DeleteNode(element) and then go to next, // element->NextSiblingElement() will be NULL. // Instead, store pointer of element we want to delete. Then // increment `element`. And THEN delete the element. tinyxml2::XMLElement* delete_this = element; element = element->NextSiblingElement(); doc.DeleteNode(delete_this); continue; } element = element->NextSiblingElement(); } // Now add them for (size_t i = 0; i < controllerButton_flip.size(); i += 1) { tinyxml2::XMLElement* msg = doc.NewElement("flipButton"); msg->LinkEndChild(doc.NewText(help.String((int) controllerButton_flip[i]).c_str())); dataNode->LinkEndChild(msg); } for (size_t i = 0; i < controllerButton_map.size(); i += 1) { tinyxml2::XMLElement* msg = doc.NewElement("enterButton"); msg->LinkEndChild(doc.NewText(help.String((int) controllerButton_map[i]).c_str())); dataNode->LinkEndChild(msg); } for (size_t i = 0; i < controllerButton_esc.size(); i += 1) { tinyxml2::XMLElement* msg = doc.NewElement("escButton"); msg->LinkEndChild(doc.NewText(help.String((int) controllerButton_esc[i]).c_str())); dataNode->LinkEndChild(msg); } for (size_t i = 0; i < controllerButton_restart.size(); i += 1) { tinyxml2::XMLElement* msg = doc.NewElement("restartButton"); msg->LinkEndChild(doc.NewText(help.String((int) controllerButton_restart[i]).c_str())); dataNode->LinkEndChild(msg); } for (size_t i = 0; i < controllerButton_interact.size(); i += 1) { tinyxml2::XMLElement* msg = doc.NewElement("interactButton"); msg->LinkEndChild(doc.NewText(help.String((int) controllerButton_interact[i]).c_str())); dataNode->LinkEndChild(msg); } xml::update_tag(dataNode, "controllerSensitivity", key.sensitivity); xml::update_tag(dataNode, "lang", loc::lang.c_str()); xml::update_tag(dataNode, "lang_set", (int) loc::lang_set); xml::update_tag(dataNode, "english_sprites", (int) loc::english_sprites); xml::update_tag(dataNode, "new_level_font", loc::new_level_font.c_str()); xml::update_tag(dataNode, "roomname_translator", (int) roomname_translator::enabled); xml::update_tag(dataNode, "checkpoint_saving", (int) checkpoint_saving); } static bool settings_loaded = false; void Game::loadsettings(struct ScreenSettings* screen_settings) { tinyxml2::XMLDocument doc; tinyxml2::XMLHandle hDoc(&doc); tinyxml2::XMLElement* dataNode; settings_loaded = true; if (!FILESYSTEM_loadTiXml2Document("saves/settings.vvv", doc)) { savesettings(screen_settings); return; } if (doc.Error()) { vlog_error("Error parsing settings.vvv: %s", doc.ErrorStr()); return; } dataNode = hDoc .FirstChildElement() .FirstChildElement("Data") .FirstChildElement() .ToElement(); deserializesettings(dataNode, screen_settings); } bool Game::savesettings(void) { struct ScreenSettings screen_settings; SDL_zero(screen_settings); gameScreen.GetSettings(&screen_settings); return savesettings(&screen_settings); } bool Game::savesettings(const struct ScreenSettings* screen_settings) { tinyxml2::XMLDocument doc; bool already_exists; if (!settings_loaded) { vlog_warn("Settings not loaded! Not writing settings.vvv."); return false; } already_exists = FILESYSTEM_loadTiXml2Document("saves/settings.vvv", doc); if (!already_exists) { vlog_info("No settings.vvv found. Creating new file"); } else if (doc.Error()) { vlog_error("Error parsing existing settings.vvv: %s", doc.ErrorStr()); vlog_info("Creating new settings.vvv"); } xml::update_declaration(doc); tinyxml2::XMLElement* root = xml::update_element(doc, "Settings"); xml::update_comment(root, " Settings (duplicated from unlock.vvv) "); tinyxml2::XMLElement* dataNode = xml::update_element(root, "Data"); serializesettings(dataNode, screen_settings); return FILESYSTEM_saveTiXml2Document("saves/settings.vvv", doc); } void Game::customstart(void) { jumpheld = true; savex = edsavex; savey = edsavey; saverx = edsaverx; savery = edsavery; savegc = edsavegc; savedir = edsavedir; //Worldmap Start savepoint = 0; gravitycontrol = savegc; setstate(0); deathseq = -1; lifeseq = 0; } void Game::start(void) { jumpheld = true; savex = 232; savey = 113; saverx = 104; savery = 110; savegc = 0; savedir = 1; //Worldmap Start savepoint = 0; gravitycontrol = savegc; setstate(0); deathseq = -1; lifeseq = 0; if (!nocutscenes) { music.play(Music_PAUSE); } } void Game::deathsequence(void) { int i; if (supercrewmate && scmhurt) { i = obj.getscm(); } else { i = obj.getplayer(); } if (INBOUNDS_VEC(i, obj.entities)) { obj.entities[i].colour = 1; obj.entities[i].invis = false; } if (deathseq == 30) { if (nodeathmode) { music.fadeout(); gameoverdelay = 60; /* Fix a bug being able to play music on the Game Over screen */ music.nicefade = false; } deathcounts++; music.playef(Sound_CRY); if (INBOUNDS_VEC(i, obj.entities) && !noflashingmode) { obj.entities[i].invis = true; } if (map.finalmode) { if (roomx - 41 >= 0 && roomx - 41 < 20 && roomy - 48 >= 0 && roomy - 48 < 20) { map.roomdeathsfinal[roomx - 41 + (20 * (roomy - 48))]++; currentroomdeaths = map.roomdeathsfinal[roomx - 41 + (20 * (roomy - 48))]; } } else { if (roomx - 100 >= 0 && roomx - 100 < 20 && roomy - 100 >= 0 && roomy - 100 < 20) { map.roomdeaths[roomx - 100 + (20*(roomy - 100))]++; currentroomdeaths = map.roomdeaths[roomx - 100 + (20 * (roomy - 100))]; } } } if (INBOUNDS_VEC(i, obj.entities) && !noflashingmode) { if (deathseq == 25) obj.entities[i].invis = true; if (deathseq == 20) obj.entities[i].invis = true; if (deathseq == 16) obj.entities[i].invis = true; if (deathseq == 14) obj.entities[i].invis = true; if (deathseq == 12) obj.entities[i].invis = true; if (deathseq < 10) obj.entities[i].invis = true; } if (!nodeathmode) { if (INBOUNDS_VEC(i, obj.entities) && deathseq <= 1) obj.entities[i].invis = false; } else { gameoverdelay--; } } void Game::startspecial( int t ) { jumpheld = true; switch(t) { case 0: //Secret Lab savex = 104; savey = 169; saverx = 118; savery = 106; savegc = 0; savedir = 1; break; case 1: //Intermission 1 (any) savex = 80; savey = 57; saverx = 41; savery = 56; savegc = 0; savedir = 0; break; default: savex = 232; savey = 113; saverx = 104; savery = 110; savegc = 0; savedir = 1; //Worldmap Start break; } savepoint = 0; gravitycontrol = savegc; setstate(0); deathseq = -1; lifeseq = 0; } void Game::starttrial( int t ) { jumpheld = true; switch(t) { case 0: //Space Station 1 savex = 200; savey = 161; saverx = 113; savery = 105; savegc = 0; savedir = 1; break; case 1: //Lab savex = 191; savey = 33; saverx = 102; savery = 116; savegc = 0; savedir = 1; break; case 2: //Tower savex = 84; savey = 193, saverx = 108; savery = 109; savegc = 0; savedir = 1; break; case 3: //Space Station 2 savex = 148; savey = 38; saverx = 112; savery = 114; savegc = 1; savedir = 0; break; case 4: //Warp savex = 52; savey = 73; saverx = 114; savery = 101; savegc = 0; savedir = 1; break; case 5: //Final savex = 101; savey = 113; saverx = 46; savery = 54; savegc = 0; savedir = 1; break; default: savex = 232; savey = 113; saverx = 104; savery = 110; savegc = 0; savedir = 1; //Worldmap Start break; } savepoint = 0; gravitycontrol = savegc; setstate(0); deathseq = -1; lifeseq = 0; } void Game::loadquick(void) { tinyxml2::XMLDocument doc; if (!FILESYSTEM_loadTiXml2Document("saves/qsave.vvv", doc)) return; readmaingamesave("qsave.vvv", doc); } void Game::readmaingamesave(const char* savename, tinyxml2::XMLDocument& doc) { tinyxml2::XMLHandle hDoc(&doc); tinyxml2::XMLElement* pElem; if (doc.Error()) { vlog_error("Error parsing %s: %s", savename, doc.ErrorStr()); return; } /* Even if we want the default hardest room to be Welcome Aboard, there are pre-2.4 * saves with JUST which should take priority over the coords */ hardestroom_x = -1; hardestroom_y = -1; hardestroom_specialname = false; hardestroom_finalstretch = false; for (pElem = hDoc .FirstChildElement() .FirstChildElement("Data") .FirstChildElement() .ToElement(); pElem != NULL; pElem = pElem->NextSiblingElement()) { const char* pKey = pElem->Value(); const char* pText = pElem->GetText(); if(pText == NULL) { pText = ""; } LOAD_ARRAY_RENAME(worldmap, map.explored) LOAD_ARRAY_RENAME(flags, obj.flags) LOAD_ARRAY(crewstats) LOAD_ARRAY_RENAME(collect, obj.collect) if (SDL_strcmp(pKey, "finalmode") == 0) { map.finalmode = help.Int(pText); } if (SDL_strcmp(pKey, "finalstretch") == 0) { map.finalstretch = help.Int(pText); } if (SDL_strcmp(pKey, "savex") == 0) { savex = help.Int(pText); } else if (SDL_strcmp(pKey, "savey") == 0) { savey = help.Int(pText); } else if (SDL_strcmp(pKey, "saverx") == 0) { saverx = help.Int(pText); } else if (SDL_strcmp(pKey, "savery") == 0) { savery = help.Int(pText); } else if (SDL_strcmp(pKey, "savegc") == 0) { savegc = help.Int(pText); } else if (SDL_strcmp(pKey, "savedir") == 0) { savedir= help.Int(pText); } else if (SDL_strcmp(pKey, "savepoint") == 0) { savepoint = help.Int(pText); } else if (SDL_strcmp(pKey, "companion") == 0) { companion = help.Int(pText); } else if (SDL_strcmp(pKey, "lastsaved") == 0) { lastsaved = help.Int(pText); } else if (SDL_strcmp(pKey, "teleportscript") == 0) { teleportscript = pText; } else if (SDL_strcmp(pKey, "supercrewmate") == 0) { supercrewmate = help.Int(pText); } else if (SDL_strcmp(pKey, "scmprogress") == 0) { scmprogress = help.Int(pText); } else if (SDL_strcmp(pKey, "frames") == 0) { frames = help.Int(pText); } else if (SDL_strcmp(pKey, "seconds") == 0) { seconds = help.Int(pText); } else if (SDL_strcmp(pKey, "minutes") == 0) { minutes = help.Int(pText); } else if (SDL_strcmp(pKey, "hours") == 0) { hours = help.Int(pText); } else if (SDL_strcmp(pKey, "deathcounts") == 0) { deathcounts = help.Int(pText); } else if (SDL_strcmp(pKey, "totalflips") == 0) { totalflips = help.Int(pText); } else if (SDL_strcmp(pKey, "hardestroom") == 0) { hardestroom = pText; } else if (SDL_strcmp(pKey, "hardestroomdeaths") == 0) { hardestroomdeaths = help.Int(pText); } else if (SDL_strcmp(pKey, "hardestroom_x") == 0) { hardestroom_x = help.Int(pText); } else if (SDL_strcmp(pKey, "hardestroom_y") == 0) { hardestroom_y = help.Int(pText); } else if (SDL_strcmp(pKey, "hardestroom_specialname") == 0) { hardestroom_specialname = help.Int(pText); } else if (SDL_strcmp(pKey, "hardestroom_finalstretch") == 0) { hardestroom_finalstretch = help.Int(pText); } else if (SDL_strcmp(pKey, "currentsong") == 0) { int song = help.Int(pText); if (song != -1) { music.play(song); } } else if (SDL_strcmp(pKey, "showtargets") == 0) { map.showtargets = help.Int(pText); } } if (map.finalmode) { map.final_colormode = false; map.final_mapcol = 0; map.final_colorframe = 0; } if (map.finalstretch) { map.finalstretch = true; map.final_colormode = true; map.final_mapcol = 0; map.final_colorframe = 1; } map.showteleporters = true; if(obj.flags[12]) map.showtargets = true; if (obj.flags[42]) map.showtrinkets = true; } void Game::customloadquick(const std::string& savfile) { tinyxml2::XMLDocument doc; tinyxml2::XMLHandle hDoc(&doc); tinyxml2::XMLElement* pElem; std::string levelfile; if (cliplaytest) { savex = playx; savey = playy; saverx = playrx; savery = playry; savegc = playgc; if (playmusic > -1) { music.play(playmusic); } return; } levelfile = savfile.substr(7); if (!FILESYSTEM_loadTiXml2Document(("saves/"+levelfile+".vvv").c_str(), doc)) { vlog_error("%s.vvv not found", levelfile.c_str()); return; } if (doc.Error()) { vlog_error("Error parsing %s.vvv: %s", levelfile.c_str(), doc.ErrorStr()); return; } // Like readmaingamesave(...), old saves have just hardestroom_x = -1; hardestroom_y = -1; hardestroom_specialname = false; hardestroom_finalstretch = false; for (pElem = hDoc .FirstChildElement() .FirstChildElement("Data") .FirstChildElement() .ToElement(); pElem != NULL; pElem = pElem->NextSiblingElement()) { const char* pKey = pElem->Value(); const char* pText = pElem->GetText() ; if(pText == NULL) { pText = ""; } LOAD_ARRAY_RENAME(worldmap, map.explored) LOAD_ARRAY_RENAME(flags, obj.flags) LOAD_ARRAY_RENAME(moods, obj.customcrewmoods) LOAD_ARRAY(crewstats) LOAD_ARRAY_RENAME(collect, obj.collect) LOAD_ARRAY_RENAME(customcollect, obj.customcollect) if (SDL_strcmp(pKey, "finalmode") == 0) { map.finalmode = help.Int(pText); } if (SDL_strcmp(pKey, "finalstretch") == 0) { map.finalstretch = help.Int(pText); } if (map.finalmode) { map.final_colormode = false; map.final_mapcol = 0; map.final_colorframe = 0; } if (map.finalstretch) { map.finalstretch = true; map.final_colormode = true; map.final_mapcol = 0; map.final_colorframe = 1; } if (SDL_strcmp(pKey, "savex") == 0) { savex = help.Int(pText); } else if (SDL_strcmp(pKey, "savey") == 0) { savey = help.Int(pText); } else if (SDL_strcmp(pKey, "saverx") == 0) { saverx = help.Int(pText); } else if (SDL_strcmp(pKey, "savery") == 0) { savery = help.Int(pText); } else if (SDL_strcmp(pKey, "savegc") == 0) { savegc = help.Int(pText); } else if (SDL_strcmp(pKey, "savedir") == 0) { savedir= help.Int(pText); } else if (SDL_strcmp(pKey, "savepoint") == 0) { savepoint = help.Int(pText); } else if (SDL_strcmp(pKey, "savecolour") == 0) { savecolour = help.Int(pText); } else if (SDL_strcmp(pKey, "companion") == 0) { companion = help.Int(pText); } else if (SDL_strcmp(pKey, "lastsaved") == 0) { lastsaved = help.Int(pText); } else if (SDL_strcmp(pKey, "teleportscript") == 0) { teleportscript = pText; } else if (SDL_strcmp(pKey, "supercrewmate") == 0) { supercrewmate = help.Int(pText); } else if (SDL_strcmp(pKey, "scmprogress") == 0) { scmprogress = help.Int(pText); } else if (SDL_strcmp(pKey, "frames") == 0) { frames = help.Int(pText); } else if (SDL_strcmp(pKey, "seconds") == 0) { seconds = help.Int(pText); } else if (SDL_strcmp(pKey, "minutes") == 0) { minutes = help.Int(pText); } else if (SDL_strcmp(pKey, "hours") == 0) { hours = help.Int(pText); } else if (SDL_strcmp(pKey, "deathcounts") == 0) { deathcounts = help.Int(pText); } else if (SDL_strcmp(pKey, "totalflips") == 0) { totalflips = help.Int(pText); } else if (SDL_strcmp(pKey, "hardestroom") == 0) { hardestroom = pText; } else if (SDL_strcmp(pKey, "hardestroomdeaths") == 0) { hardestroomdeaths = help.Int(pText); } else if (SDL_strcmp(pKey, "hardestroom_x") == 0) { hardestroom_x = help.Int(pText); } else if (SDL_strcmp(pKey, "hardestroom_y") == 0) { hardestroom_y = help.Int(pText); } else if (SDL_strcmp(pKey, "hardestroom_specialname") == 0) { hardestroom_specialname = help.Int(pText); } else if (SDL_strcmp(pKey, "hardestroom_finalstretch") == 0) { hardestroom_finalstretch = help.Int(pText); } else if (SDL_strcmp(pKey, "currentsong") == 0) { int song = help.Int(pText); if (song != -1) { music.play(song); } } else if (SDL_strcmp(pKey, "lang_custom") == 0) { loc::lang_custom = pText; if (pText[0] != '\0') { loc::loadtext_custom(NULL); } } else if (SDL_strcmp(pKey, "showminimap") == 0) { map.customshowmm = help.Int(pText); } else if (SDL_strcmp(pKey, "mapreveal") == 0) { map.revealmap = help.Int(pText); } else if (SDL_strcmp(pKey, "disabletemporaryaudiopause") == 0) { disabletemporaryaudiopause = help.Int(pText); } else if (SDL_strcmp(pKey, "showtrinkets") == 0) { map.showtrinkets = help.Int(pText); } else if (SDL_strcmp(pKey, "roomname") == 0) { map.setroomname(pText); map.roomnameset = true; map.roomname_special = true; } } } static void loadthissummary( const char* filename, struct Game::Summary* summary, tinyxml2::XMLDocument& doc ) { tinyxml2::XMLHandle hDoc(&doc); tinyxml2::XMLElement* pElem; if (doc.Error()) { vlog_error("Error parsing %s: %s", filename, doc.ErrorStr()); return; } summary->exists = true; for (pElem = hDoc .FirstChildElement() .FirstChildElement("Data") .FirstChildElement() .ToElement(); pElem != NULL; pElem = pElem->NextSiblingElement()) { const char* pKey = pElem->Value(); const char* pText = pElem->GetText(); if (pText == NULL) { pText = ""; } if (SDL_strcmp(pKey, "seconds") == 0) { summary->seconds = help.Int(pText); } else if (SDL_strcmp(pKey, "minutes") == 0) { summary->minutes = help.Int(pText); } else if (SDL_strcmp(pKey, "hours") == 0) { summary->hours = help.Int(pText); } else if (SDL_strcmp(pKey, "saverx") == 0) { summary->saverx = help.Int(pText); } else if (SDL_strcmp(pKey, "savery") == 0) { summary->savery = help.Int(pText); } else if (SDL_strcmp(pKey, "trinkets") == 0) { summary->trinkets = help.Int(pText); } LOAD_ARRAY_RENAME(crewstats, summary->crewstats) } } void Game::loadsummary(void) { tinyxml2::XMLDocument doc; SDL_zero(last_telesave); SDL_zero(last_quicksave); if (FILESYSTEM_loadTiXml2Document("saves/tsave.vvv", doc)) { loadthissummary("tsave.vvv", &last_telesave, doc); } if (FILESYSTEM_loadTiXml2Document("saves/qsave.vvv", doc)) { loadthissummary("qsave.vvv", &last_quicksave, doc); } } void Game::initteleportermode(void) { //Set the teleporter variable to the right position! teleport_to_teleporter = 0; for (size_t i = 0; i < map.teleporters.size(); i++) { if (roomx == map.teleporters[i].x + 100 && roomy == map.teleporters[i].y + 100) { teleport_to_teleporter = i; } } } bool Game::savetele(void) { if (map.custommode || inspecial()) { //Don't trash save data! return false; } tinyxml2::XMLDocument doc; bool already_exists = FILESYSTEM_loadTiXml2Document("saves/tsave.vvv", doc); if (!already_exists) { vlog_info("No tsave.vvv found. Creating new file"); } else if (doc.Error()) { vlog_error("Error parsing existing tsave.vvv: %s", doc.ErrorStr()); vlog_info("Creating new tsave.vvv"); } last_telesave = writemaingamesave(doc); if(!FILESYSTEM_saveTiXml2Document("saves/tsave.vvv", doc)) { vlog_error("Could Not Save game!"); vlog_error("Failed: %s%s", saveFilePath, "tsave.vvv"); return false; } vlog_info("Game saved"); return true; } bool Game::savequick(void) { if (map.custommode || inspecial()) { //Don't trash save data! return false; } tinyxml2::XMLDocument doc; bool already_exists = FILESYSTEM_loadTiXml2Document("saves/qsave.vvv", doc); if (!already_exists) { vlog_info("No qsave.vvv found. Creating new file"); } else if (doc.Error()) { vlog_error("Error parsing existing qsave.vvv: %s", doc.ErrorStr()); vlog_info("Creating new qsave.vvv"); } last_quicksave = writemaingamesave(doc); if(!FILESYSTEM_saveTiXml2Document("saves/qsave.vvv", doc)) { vlog_error("Could Not Save game!"); vlog_error("Failed: %s%s", saveFilePath, "qsave.vvv"); return false; } vlog_info("Game saved"); return true; } // Returns summary of save struct Game::Summary Game::writemaingamesave(tinyxml2::XMLDocument& doc) { //TODO make this code a bit cleaner. struct Game::Summary summary; SDL_zero(summary); if (map.custommode || inspecial()) { //Don't trash save data! return summary; } xml::update_declaration(doc); tinyxml2::XMLElement * root = xml::update_element(doc, "Save"); xml::update_comment(root, " Save file " ); tinyxml2::XMLElement * msgs = xml::update_element(root, "Data"); //Flags, map and stats std::string mapExplored; for(size_t i = 0; i < SDL_arraysize(map.explored); i++ ) { mapExplored += help.String(map.explored[i]) + ","; } xml::update_tag(msgs, "worldmap", mapExplored.c_str()); std::string flags; for(size_t i = 0; i < SDL_arraysize(obj.flags); i++ ) { flags += help.String((int) obj.flags[i]) + ","; } xml::update_tag(msgs, "flags", flags.c_str()); std::string crewstatsString; for(size_t i = 0; i < SDL_arraysize(crewstats); i++ ) { crewstatsString += help.String(crewstats[i]) + ","; } xml::update_tag(msgs, "crewstats", crewstatsString.c_str()); std::string collect; for(size_t i = 0; i < SDL_arraysize(obj.collect); i++ ) { collect += help.String((int) obj.collect[i]) + ","; } xml::update_tag(msgs, "collect", collect.c_str()); //Position xml::update_tag(msgs, "savex", savex); xml::update_tag(msgs, "savey", savey); xml::update_tag(msgs, "saverx", saverx); xml::update_tag(msgs, "savery", savery); xml::update_tag(msgs, "savegc", savegc); xml::update_tag(msgs, "savedir", savedir); xml::update_tag(msgs, "savepoint", savepoint); int n_trinkets = trinkets(); xml::update_tag(msgs, "trinkets", n_trinkets); //Special stats if (music.nicefade) { xml::update_tag(msgs, "currentsong", music.nicechange); } else { xml::update_tag(msgs, "currentsong", music.currentsong); } xml::update_tag(msgs, "showtargets", (int) map.showtargets); xml::update_tag(msgs, "teleportscript", teleportscript.c_str()); xml::update_tag(msgs, "companion", companion); xml::update_tag(msgs, "lastsaved", lastsaved); xml::update_tag(msgs, "supercrewmate", (int) supercrewmate); xml::update_tag(msgs, "scmprogress", scmprogress); xml::update_tag(msgs, "frames", frames); xml::update_tag(msgs, "seconds", seconds); xml::update_tag(msgs, "minutes", minutes); xml::update_tag(msgs, "hours", hours); xml::update_tag(msgs, "deathcounts", deathcounts); xml::update_tag(msgs, "totalflips", totalflips); xml::update_tag(msgs, "hardestroom", hardestroom.c_str()); xml::update_tag(msgs, "hardestroomdeaths", hardestroomdeaths); xml::update_tag(msgs, "hardestroom_x", hardestroom_x); xml::update_tag(msgs, "hardestroom_y", hardestroom_y); xml::update_tag(msgs, "hardestroom_specialname", (int) hardestroom_specialname); xml::update_tag(msgs, "hardestroom_finalstretch", (int) hardestroom_finalstretch); xml::update_tag(msgs, "finalmode", (int) map.finalmode); xml::update_tag(msgs, "finalstretch", (int) map.finalstretch); std::string legacy_summary = std::string(map.currentarea(saverx, savery)) + ", " + timestring(); xml::update_tag(msgs, "summary", legacy_summary.c_str()); summary.exists = true; summary.seconds = seconds; summary.minutes = minutes; summary.hours = hours; summary.saverx = saverx; summary.savery = savery; summary.trinkets = n_trinkets; SDL_memcpy(summary.crewstats, crewstats, sizeof(summary.crewstats)); return summary; } bool Game::customsavequick(const std::string& savfile) { const std::string levelfile = savfile.substr(7); tinyxml2::XMLDocument doc; bool already_exists = FILESYSTEM_loadTiXml2Document(("saves/" + levelfile + ".vvv").c_str(), doc); if (!already_exists) { vlog_info("No %s.vvv found. Creating new file", levelfile.c_str()); } else if (doc.Error()) { vlog_error("Error parsing existing %s.vvv: %s", levelfile.c_str(), doc.ErrorStr()); vlog_info("Creating new %s.vvv", levelfile.c_str()); } xml::update_declaration(doc); tinyxml2::XMLElement * root = xml::update_element(doc, "Save"); xml::update_comment(root, " Save file "); tinyxml2::XMLElement * msgs = xml::update_element(root, "Data"); //Flags, map and stats std::string mapExplored; for(size_t i = 0; i < SDL_arraysize(map.explored); i++ ) { mapExplored += help.String(map.explored[i]) + ","; } xml::update_tag(msgs, "worldmap", mapExplored.c_str()); std::string flags; for(size_t i = 0; i < SDL_arraysize(obj.flags); i++ ) { flags += help.String((int) obj.flags[i]) + ","; } xml::update_tag(msgs, "flags", flags.c_str()); std::string moods; for(size_t i = 0; i < SDL_arraysize(obj.customcrewmoods); i++ ) { moods += help.String(obj.customcrewmoods[i]) + ","; } xml::update_tag(msgs, "moods", moods.c_str()); std::string crewstatsString; for(size_t i = 0; i < SDL_arraysize(crewstats); i++ ) { crewstatsString += help.String(crewstats[i]) + ","; } xml::update_tag(msgs, "crewstats", crewstatsString.c_str()); std::string collect; for(size_t i = 0; i < SDL_arraysize(obj.collect); i++ ) { collect += help.String((int) obj.collect[i]) + ","; } xml::update_tag(msgs, "collect", collect.c_str()); std::string customcollect; for(size_t i = 0; i < SDL_arraysize(obj.customcollect); i++ ) { customcollect += help.String((int) obj.customcollect[i]) + ","; } xml::update_tag(msgs, "customcollect", customcollect.c_str()); //Position xml::update_tag(msgs, "savex", savex); xml::update_tag(msgs, "savey", savey); xml::update_tag(msgs, "saverx", saverx); xml::update_tag(msgs, "savery", savery); xml::update_tag(msgs, "savegc", savegc); xml::update_tag(msgs, "savedir", savedir); xml::update_tag(msgs, "savepoint", savepoint); xml::update_tag(msgs, "savecolour", savecolour); xml::update_tag(msgs, "trinkets", trinkets()); xml::update_tag(msgs, "crewmates", crewmates()); //Special stats if (music.nicefade) { xml::update_tag(msgs, "currentsong", music.nicechange ); } else { xml::update_tag(msgs, "currentsong", music.currentsong); } xml::update_tag(msgs, "lang_custom", loc::lang_custom.c_str()); xml::update_tag(msgs, "teleportscript", teleportscript.c_str()); xml::update_tag(msgs, "companion", companion); xml::update_tag(msgs, "lastsaved", lastsaved); xml::update_tag(msgs, "supercrewmate", (int) supercrewmate); xml::update_tag(msgs, "scmprogress", scmprogress); xml::update_tag(msgs, "frames", frames); xml::update_tag(msgs, "seconds", seconds); xml::update_tag(msgs, "minutes", minutes); xml::update_tag(msgs, "hours", hours); xml::update_tag(msgs, "deathcounts", deathcounts); xml::update_tag(msgs, "totalflips", totalflips); xml::update_tag(msgs, "hardestroom", hardestroom.c_str()); xml::update_tag(msgs, "hardestroomdeaths", hardestroomdeaths); xml::update_tag(msgs, "hardestroom_x", hardestroom_x); xml::update_tag(msgs, "hardestroom_y", hardestroom_y); xml::update_tag(msgs, "hardestroom_specialname", (int) hardestroom_specialname); xml::update_tag(msgs, "hardestroom_finalstretch", (int) hardestroom_finalstretch); xml::update_tag(msgs, "showminimap", (int) map.customshowmm); xml::update_tag(msgs, "mapreveal", (int) map.revealmap); xml::update_tag(msgs, "disabletemporaryaudiopause", (int) disabletemporaryaudiopause); xml::update_tag(msgs, "showtrinkets", (int) map.showtrinkets); if (map.roomnameset) { xml::update_tag(msgs, "roomname", map.roomname); } else { // If there's roomname tags, remove them. There will probably only always be one, but just in case... tinyxml2::XMLElement* element; while ((element = msgs->FirstChildElement("roomname")) != NULL) { doc.DeleteNode(element); } } std::string legacy_summary = customleveltitle + ", " + timestring(); xml::update_tag(msgs, "summary", legacy_summary.c_str()); if(!FILESYSTEM_saveTiXml2Document(("saves/"+levelfile+".vvv").c_str(), doc)) { vlog_error("Could Not Save game!"); vlog_error("Failed: %s%s%s", saveFilePath, levelfile.c_str(), ".vvv"); return false; } vlog_info("Game saved"); return true; } void Game::loadtele(void) { tinyxml2::XMLDocument doc; if (!FILESYSTEM_loadTiXml2Document("saves/tsave.vvv", doc)) return; readmaingamesave("tsave.vvv", doc); } std::string Game::unrescued(void) { //Randomly return the name of an unrescued crewmate //Localization is handled with regular cutscene dialogue if (fRandom() * 100 > 50) { if (!crewstats[5]) return "Victoria"; if (!crewstats[2]) return "Vitellary"; if (!crewstats[4]) return "Verdigris"; if (!crewstats[3]) return "Vermilion"; } else { if (fRandom() * 100 > 50) { if (!crewstats[2]) return "Vitellary"; if (!crewstats[4]) return "Verdigris"; if (!crewstats[3]) return "Vermilion"; if (!crewstats[5]) return "Victoria"; } else { if (!crewstats[4]) return "Verdigris"; if (!crewstats[3]) return "Vermilion"; if (!crewstats[5]) return "Victoria"; if (!crewstats[2]) return "Vitellary"; } } return "you"; } void Game::gameclock(void) { if (timetrialcountdown > 0) { return; } frames++; if (frames >= 30) { frames -= 30; seconds++; if (seconds >= 60) { seconds -= 60; minutes++; if (minutes >= 60) { minutes -= 60; hours++; } } } } std::string Game::giventimestring( int hrs, int min, int sec ) { return timetstring(help.hms_to_seconds(hrs, min, sec)); } std::string Game::timestring(void) { return giventimestring(hours, minutes, seconds); } std::string Game::resulttimestring(void) { //given result time in seconds: char output[SCREEN_WIDTH_CHARS + 1]; help.format_time(output, sizeof(output), timetrialresulttime, timetrialresultframes, true); return output; } std::string Game::timetstring( int t ) { //given par time in seconds: char output[SCREEN_WIDTH_CHARS + 1]; help.format_time(output, sizeof(output), t, -1, true); return output; } void Game::timestringcenti(char* buffer, const size_t buffer_size) { help.format_time(buffer, buffer_size, help.hms_to_seconds(hours, minutes, seconds), frames, true); } void Game::returnmenu(void) { if (menustack.empty()) { vlog_error("Error: returning to previous menu frame on empty stack!"); return; } /* FIXME: Super bad kludge, don't hardcode this! */ if (currentmenuname == Menu::ed_music) { music.fadeout(); } else if (currentmenuname == Menu::gamecompletecontinue || currentmenuname == Menu::timetrialcomplete3 || currentmenuname == Menu::gameover2 || currentmenuname == Menu::nodeathmodecomplete2) { music.play(Music_PRESENTINGVVVVVV); } enum Menu::MenuName camefrom = currentmenuname; MenuStackFrame& frame = menustack[menustack.size()-1]; //Store this in case createmenu() removes the stack frame int previousoption = frame.option; createmenu(frame.name, true); currentmenuoption = previousoption; //Remove the stackframe now, but createmenu() might have already gotten to it //if we were returning to the main menu if (!menustack.empty()) { menustack.pop_back(); } /* FIXME: Even more horrible kludge! */ if (camefrom == Menu::timetrialcomplete3) { if (can_unlock_ndm()) { unlock_ndm(); } } } void Game::returntomenu(enum Menu::MenuName t) { if (currentmenuname == t) { createmenu(t, true); return; } //Unwind the menu stack until we reach our desired menu int i = menustack.size() - 1; while (i >= 0) { //If we pop it off we can't reference it anymore, so check for it now bool is_the_menu_we_want = menustack[i].name == t; returnmenu(); if (is_the_menu_we_want) { break; } i--; } } void Game::createmenu( enum Menu::MenuName t, bool samemenu/*= false*/ ) { if (t == Menu::mainmenu && !menutestmode) { //Either we've just booted up the game or returned from gamemode //Whichever it is, we shouldn't have a stack, //and most likely don't have a current stackframe menustack.clear(); } else if (!samemenu) { MenuStackFrame frame; frame.option = currentmenuoption; frame.name = currentmenuname; menustack.push_back(frame); currentmenuoption = 0; } currentmenuname = t; menuyoff = 0; int maxspacing = 30; // maximum value for menuspacing, can only become lower. menucountdown = 0; menuoptions.clear(); switch (t) { case Menu::mainmenu: if (ingame_titlemode) { /* We shouldn't be here! */ SDL_assert(0 && "Entering main menu from in-game options!"); break; } #if !defined(MAKEANDPLAY) option(loc::gettext("play")); #endif option(loc::gettext("levels")); option(loc::gettext("options")); if (loc::show_translator_menu) { option(loc::gettext("translator")); } option(loc::gettext("credits")); option(loc::gettext("quit")); menuyoff = -10; maxspacing = 15; break; case Menu::playerworlds: option(loc::gettext("play a level")); option(loc::gettext("level editor"), !editor_disabled); if (!editor_disabled) { option(loc::gettext("open level folder"), FILESYSTEM_openDirectoryEnabled()); option(loc::gettext("show level folder path")); } option(loc::gettext("return")); menuyoff = -40; maxspacing = 15; break; case Menu::confirmshowlevelspath: option(loc::gettext("no, don't show me")); option(loc::gettext("yes, reveal the path")); menuyoff = -10; break; case Menu::showlevelspath: option(loc::gettext("return to levels")); menuyoff = 60; break; case Menu::levellist: if(cl.ListOfMetaData.size()==0) { option(loc::gettext("ok")); menuyoff = -20; } else { for(int i=0; i<(int) cl.ListOfMetaData.size(); i++) // FIXME: int/size_t! -flibit { if(i>=levelpage*8 && i< (levelpage*8)+8) { const std::string filename = cl.ListOfMetaData[i].filename.substr(7); int score = 0; if (customlevelstats.count(filename) > 0) { score = customlevelstats[filename]; } const char* prefix; switch (score) { case 0: { static const char tmp[] = " "; prefix = tmp; break; } case 1: { static const char tmp[] = " * "; prefix = tmp; break; } case 3: { static const char tmp[] = "** "; prefix = tmp; break; } default: SDL_assert(0 && "Unhandled menu text prefix!"); prefix = ""; break; } const char* title = cl.ListOfMetaData[i].title.c_str(); if (cl.ListOfMetaData[i].title_is_gettext) { title = loc::gettext(title); } /* We have to make sure the stars and spaces are consistently on the * correct side of the title, no matter what bidi characters are in there. * So just always let the bidi engine handle it, with a few control chars. */ char text[MENU_TEXT_BYTES]; SDL_snprintf( text, sizeof(text), "%s%s%s%s%s", // LRM or RLM depending on UI language, to make the stars aligned to left or right UTF8_encode(font::is_rtl(PR_FONT_INTERFACE) ? 0x200F : 0x200E).bytes, prefix, // FIRST STRONG ISOLATE, to start an isolated block oriented however bidi sees fit UTF8_encode(0x2068).bytes, title, // POP DIRECTIONAL ISOLATE, exit isolated level title UTF8_encode(0x2069).bytes ); for (size_t ii = 0; text[ii] != '\0'; ++ii) { text[ii] = SDL_tolower(text[ii]); } option( text, true, (cl.ListOfMetaData[i].title_is_gettext ? PR_FONT_INTERFACE : PR_FONT_IDX( cl.ListOfMetaData[i].level_main_font_idx, font::is_rtl(PR_FONT_INTERFACE) )) | PR_RTL_XFLIP ); } } if (cl.ListOfMetaData.size() > 8) { if((size_t) ((levelpage*8)+8) = 3 && !unlocknotify[Unlock_TIMETRIAL_SPACESTATION1]) { temp++; } if (unlock[Unlock_LABORATORY_COMPLETE] && stat_trinkets >= 6 && !unlocknotify[Unlock_TIMETRIAL_LABORATORY]) { temp++; } if (unlock[Unlock_TOWER_COMPLETE] && stat_trinkets >= 9 && !unlocknotify[Unlock_TIMETRIAL_TOWER]) { temp++; } if (unlock[Unlock_SPACESTATION2_COMPLETE] && stat_trinkets >= 12 && !unlocknotify[Unlock_TIMETRIAL_SPACESTATION2]) { temp++; } if (unlock[Unlock_WARPZONE_COMPLETE] && stat_trinkets >= 15 && !unlocknotify[Unlock_TIMETRIAL_WARPZONE]) { temp++; } if (unlock[UnlockTrophy_GAME_COMPLETE] && stat_trinkets >= 18 && !unlocknotify[Unlock_TIMETRIAL_FINALLEVEL]) { temp++; } if (temp > 0) { //you've unlocked a time trial! if (unlock[Unlock_SPACESTATION1_COMPLETE] && stat_trinkets >= 3) { unlocknotify[Unlock_TIMETRIAL_SPACESTATION1] = true; unlock[Unlock_TIMETRIAL_SPACESTATION1] = true; } if (unlock[Unlock_LABORATORY_COMPLETE] && stat_trinkets >= 6) { unlocknotify[Unlock_TIMETRIAL_LABORATORY] = true; unlock[Unlock_TIMETRIAL_LABORATORY] = true; } if (unlock[Unlock_TOWER_COMPLETE] && stat_trinkets >= 9) { unlocknotify[Unlock_TIMETRIAL_TOWER] = true; unlock[Unlock_TIMETRIAL_TOWER] = true; } if (unlock[Unlock_SPACESTATION2_COMPLETE] && stat_trinkets >= 12) { unlocknotify[Unlock_TIMETRIAL_SPACESTATION2] = true; unlock[Unlock_TIMETRIAL_SPACESTATION2] = true; } if (unlock[Unlock_WARPZONE_COMPLETE] && stat_trinkets >= 15) { unlocknotify[Unlock_TIMETRIAL_WARPZONE] = true; unlock[Unlock_TIMETRIAL_WARPZONE] = true; } if (unlock[UnlockTrophy_GAME_COMPLETE] && stat_trinkets >= 18) { unlocknotify[Unlock_TIMETRIAL_FINALLEVEL] = true; unlock[Unlock_TIMETRIAL_FINALLEVEL] = true; } if (temp == 1) { createmenu(Menu::unlocktimetrial, true); savestatsandsettings(); } else if (temp > 1) { createmenu(Menu::unlocktimetrials, true); savestatsandsettings(); } } else { //Alright, we haven't unlocked any time trials. How about no death mode? if (can_unlock_ndm()) { unlock_ndm(); } //Alright then! Flip mode? else if (unlock[UnlockTrophy_GAME_COMPLETE] && !unlocknotify[Unlock_FLIPMODE]) { unlock[Unlock_FLIPMODE] = true; unlocknotify[Unlock_FLIPMODE] = true; createmenu(Menu::unlockflipmode, true); savestatsandsettings(); } //What about the intermission levels? else if (unlock[Unlock_INTERMISSION2_COMPLETE] && !unlocknotify[Unlock_INTERMISSION_REPLAYS]) { unlock[Unlock_INTERMISSION_REPLAYS] = true; unlocknotify[Unlock_INTERMISSION_REPLAYS] = true; createmenu(Menu::unlockintermission, true); savestatsandsettings(); } else { if (save_exists()) { option(loc::gettext("continue")); } else { option(loc::gettext("new game")); } //ok, secret lab! no notification, but test: if (unlock[Unlock_SECRETLAB]) { option(loc::gettext("secret lab")); } option(loc::gettext("play modes")); if (save_exists()) { option(loc::gettext("new game")); } option(loc::gettext("return")); if (unlock[Unlock_SECRETLAB]) { menuyoff = -30; } else { menuyoff = -40; } } } break; } case Menu::unlocktimetrial: case Menu::unlocktimetrials: case Menu::unlocknodeathmode: case Menu::unlockintermission: case Menu::unlockflipmode: option(loc::gettext("proceed")); menuyoff = 70; break; case Menu::newgamewarning: option(loc::gettext("start new game")); option(loc::gettext("return")); menuyoff = 64; break; case Menu::playmodes: option(loc::gettext("time trials"), !nocompetitive_unless_translator()); option(loc::gettext("intermissions"), unlock[Unlock_INTERMISSION_REPLAYS]); option(loc::gettext("no death mode"), unlock[Unlock_NODEATHMODE] && !nocompetitive()); option(loc::gettext("flip mode"), unlock[Unlock_FLIPMODE]); option(loc::gettext("return")); menuyoff = 8; maxspacing = 20; break; case Menu::intermissionmenu: option(loc::gettext("play intermission 1")); option(loc::gettext("play intermission 2")); option(loc::gettext("return")); menuyoff = -35; break; case Menu::playint1: start_translator_exploring = false; option(loc::gettext_case("Vitellary", 1)); option(loc::gettext_case("Vermilion", 1)); option(loc::gettext_case("Verdigris", 1)); option(loc::gettext_case("Victoria", 1)); option(loc::gettext("return")); menuyoff = 10; break; case Menu::playint2: start_translator_exploring = false; option(loc::gettext_case("Vitellary", 1)); option(loc::gettext_case("Vermilion", 1)); option(loc::gettext_case("Verdigris", 1)); option(loc::gettext_case("Victoria", 1)); option(loc::gettext("return")); menuyoff = 10; break; case Menu::continuemenu: map.settowercolour(3); option(loc::gettext("continue from teleporter")); option(loc::gettext("continue from quicksave")); option(loc::gettext("return")); menuyoff = 20; break; case Menu::startnodeathmode: option(loc::gettext("disable cutscenes")); option(loc::gettext("enable cutscenes")); option(loc::gettext("return")); menuyoff = 40; break; case Menu::gameover: menucountdown = 120; menudest=Menu::gameover2; break; case Menu::gameover2: option(loc::gettext("return to play menu")); menuyoff = 80; break; case Menu::unlockmenutrials: option(loc::gettext("space station 1"), !unlock[Unlock_TIMETRIAL_SPACESTATION1]); option(loc::gettext("the laboratory"), !unlock[Unlock_TIMETRIAL_LABORATORY]); option(loc::gettext("the tower"), !unlock[Unlock_TIMETRIAL_TOWER]); option(loc::gettext("space station 2"), !unlock[Unlock_TIMETRIAL_SPACESTATION2]); option(loc::gettext("the warp zone"), !unlock[Unlock_TIMETRIAL_WARPZONE]); option(loc::gettext("the final level"), !unlock[Unlock_TIMETRIAL_FINALLEVEL]); option(loc::gettext("return")); menuyoff = 0; break; case Menu::timetrials: option(loc::gettext(unlock[Unlock_TIMETRIAL_SPACESTATION1] ? "space station 1" : "???"), unlock[Unlock_TIMETRIAL_SPACESTATION1]); option(loc::gettext(unlock[Unlock_TIMETRIAL_LABORATORY] ? "the laboratory" : "???"), unlock[Unlock_TIMETRIAL_LABORATORY]); option(loc::gettext(unlock[Unlock_TIMETRIAL_TOWER] ? "the tower" : "???"), unlock[Unlock_TIMETRIAL_TOWER]); option(loc::gettext(unlock[Unlock_TIMETRIAL_SPACESTATION2] ? "space station 2" : "???"), unlock[Unlock_TIMETRIAL_SPACESTATION2]); option(loc::gettext(unlock[Unlock_TIMETRIAL_WARPZONE] ? "the warp zone" : "???"), unlock[Unlock_TIMETRIAL_WARPZONE]); option(loc::gettext(unlock[Unlock_TIMETRIAL_FINALLEVEL] ? "the final level" : "???"), unlock[Unlock_TIMETRIAL_FINALLEVEL]); option(loc::gettext("return")); menuyoff = 0; maxspacing = 15; break; case Menu::nodeathmodecomplete: menucountdown = 90; menudest = Menu::nodeathmodecomplete2; break; case Menu::nodeathmodecomplete2: option(loc::gettext("return to play menu")); menuyoff = 70; break; case Menu::timetrialcomplete: menucountdown = 90; menudest=Menu::timetrialcomplete2; break; case Menu::timetrialcomplete2: menucountdown = 60; menudest=Menu::timetrialcomplete3; break; case Menu::timetrialcomplete3: option(loc::gettext("return to play menu")); option(loc::gettext("try again")); menuyoff = 70; break; case Menu::gamecompletecontinue: option(loc::gettext("return to play menu")); menuyoff = 70; break; case Menu::errorsavingsettings: option(loc::gettext("ok")); option(loc::gettext("silence")); menuyoff = 10; break; case Menu::errorloadinglevel: case Menu::warninglevellist: option(loc::gettext("ok")); menuyoff = 50; break; } // Automatically center the menu. We must check the width of the menu with the initial horizontal spacing. // If it's too wide, reduce the horizontal spacing by 5 and retry. // Try to limit the menu width to 272 pixels: 320 minus 16*2 for square brackets, minus 8*2 padding. // The square brackets fall outside the menu width (i.e. selected menu options are printed 16 pixels to the left) bool done_once = false; int menuwidth = 0; for (; !done_once || (menuwidth > 272 && menuspacing > 0); maxspacing -= 5) { done_once = true; menuspacing = maxspacing; menuwidth = 0; for (size_t i = 0; i < menuoptions.size(); i++) { int width = i*menuspacing + font::len(menuoptions[i].print_flags, menuoptions[i].text); if (width > menuwidth) menuwidth = width; } } menuxoff = (320-menuwidth)/2; } bool Game::can_unlock_ndm(void) { int temp = 0; if (bestrank[TimeTrial_SPACESTATION1] >= 2) temp++; if (bestrank[TimeTrial_LABORATORY] >= 2) temp++; if (bestrank[TimeTrial_TOWER] >= 2) temp++; if (bestrank[TimeTrial_SPACESTATION2] >= 2) temp++; if (bestrank[TimeTrial_WARPZONE] >= 2) temp++; if (bestrank[TimeTrial_FINALLEVEL] >= 2) temp++; return temp >= 4 && !unlocknotify[Unlock_NODEATHMODE]; } void Game::unlock_ndm(void) { unlocknotify[Unlock_NODEATHMODE] = true; unlock[Unlock_NODEATHMODE] = true; createmenu(Menu::unlocknodeathmode, true); savestatsandsettings(); } void Game::deletequick(void) { if (inspecial() || map.custommode) { return; } if (!FILESYSTEM_delete("saves/qsave.vvv")) { vlog_error("Error deleting saves/qsave.vvv"); } else { SDL_zero(last_quicksave); } } void Game::deletetele(void) { if (inspecial() || map.custommode) { return; } if (!FILESYSTEM_delete("saves/tsave.vvv")) { vlog_error("Error deleting saves/tsave.vvv"); } else { SDL_zero(last_telesave); } } void Game::customdeletequick(const std::string& file) { const std::string path = "saves/" + file.substr(7) + ".vvv"; if (!FILESYSTEM_delete(path.c_str())) { vlog_error("Error deleting %s", path.c_str()); } } void Game::swnpenalty(void) { //set the SWN clock back to the closest 5 second interval if (swntimer <= 150) { swntimer += 8; if (swntimer > 150) swntimer = 150; } else if (swntimer <= 300) { swntimer += 8; if (swntimer > 300) swntimer = 300; } else if (swntimer <= 450) { swntimer += 8; if (swntimer > 450) swntimer = 450; } else if (swntimer <= 600) { swntimer += 8; if (swntimer > 600) swntimer = 600; } else if (swntimer <= 750) { swntimer += 8; if (swntimer > 750) swntimer = 750; } else if (swntimer <= 900) { swntimer += 8; if (swntimer > 900) swntimer = 900; } else if (swntimer <= 1050) { swntimer += 8; if (swntimer > 1050) swntimer = 1050; } else if (swntimer <= 1200) { swntimer += 8; if (swntimer > 1200) swntimer = 1200; } else if (swntimer <= 1350) { swntimer += 8; if (swntimer > 1350) swntimer = 1350; } else if (swntimer <= 1500) { swntimer += 8; if (swntimer > 1500) swntimer = 1500; } else if (swntimer <= 1650) { swntimer += 8; if (swntimer > 1650) swntimer = 1650; } else if (swntimer <= 1800) { swntimer += 8; if (swntimer > 1800) swntimer = 1800; } else if (swntimer <= 2100) { swntimer += 8; if (swntimer > 2100) swntimer = 2100; } else if (swntimer <= 2400) { swntimer += 8; if (swntimer > 2400) swntimer = 2400; } } int Game::crewrescued(void) { int temp = 0; for (size_t i = 0; i < SDL_arraysize(crewstats); i++) { if (crewstats[i]) { temp++; } } return temp; } void Game::resetgameclock(void) { frames = 0; seconds = 0; minutes = 0; hours = 0; } int Game::trinkets(void) { int temp = 0; for (size_t i = 0; i < SDL_arraysize(obj.collect); i++) { if (obj.collect[i]) { temp++; } } return temp; } int Game::crewmates(void) { int temp = 0; for (size_t i = 0; i < SDL_arraysize(obj.customcollect); i++) { if (obj.customcollect[i]) { temp++; } } return temp; } bool Game::anything_unlocked(void) { for (size_t i = 0; i < SDL_arraysize(unlock); i++) { if (unlock[i] && (i == 8 // Secret Lab || (i >= 9 && i <= 14) // any Time Trial || i == 16 // Intermission replays || i == 17 // No Death Mode || i == 18)) // Flip Mode { return true; } } return false; } bool Game::save_exists(void) { return last_telesave.exists || last_quicksave.exists; } static void hardreset(void) { script.hardreset(); } static void returntoeditor_callback(void) { extern Game game; game.returntoeditor(); ed.show_note(loc::gettext("Level quits to menu")); } void Game::quittomenu(void) { if (gamestate != EDITORMODE && map.custommode && !map.custommodeforreal) { /* We are playtesting! Go back to the editor * instead of losing unsaved changes. */ /* This needs to be deferred, otherwise some state would persist. */ DEFER_CALLBACK(returntoeditor_callback); return; } gamestate = TITLEMODE; graphics.fademode = FADE_START_FADEIN; FILESYSTEM_unmountAssets(); loc::unloadtext_custom(); font::unload_custom(); cliplaytest = false; graphics.titlebg.tdrawback = true; graphics.flipmode = false; //Don't be stuck on the summary screen, //or "who do you want to play the level with?" //or "do you want cutscenes?" //or the confirm-load-quicksave menu if (translator_cutscene_test) { returntomenu(Menu::translator_options_cutscenetest); } else if (translator_exploring) { returntomenu(Menu::translator_options_exploregame); } else if (intimetrial) { returntomenu(Menu::timetrials); } else if (inintermission) { returntomenu(Menu::intermissionmenu); } else if (nodeathmode) { returntomenu(Menu::playmodes); } else if (map.custommode) { if (map.custommodeforreal) { returntomenu(Menu::levellist); } else { //Returning from editor editor_disabled = !BUTTONGLYPHS_keyboard_is_available(); returntomenu(Menu::playerworlds); } } else if (save_exists() || anything_unlocked()) { returntomenu(Menu::play); if (!insecretlab) { //Select "continue" currentmenuoption = 0; } } else { createmenu(Menu::mainmenu); } /* We might not be at the end of the frame yet. * If we hardreset() now, some state might still persist. */ DEFER_CALLBACK(hardreset); } void Game::returntolab(void) { gamestate = GAMEMODE; graphics.fademode = FADE_START_FADEIN; map.gotoroom(119, 107); int player = obj.getplayer(); if (INBOUNDS_VEC(player, obj.entities)) { obj.entities[player].xp = 132; obj.entities[player].yp = 137; } gravitycontrol = 0; savepoint = 0; saverx = 119; savery = 107; savex = 132; savey = 137; savegc = 0; if (INBOUNDS_VEC(player, obj.entities)) { savedir = obj.entities[player].dir; } music.play(Music_PIPEDREAM); } static void resetbg(void) { graphics.backgrounddrawn = false; } void Game::returntoeditor(void) { gamestate = EDITORMODE; graphics.textboxes.clear(); hascontrol = true; advancetext = false; completestop = false; setstate(0); graphics.showcutscenebars = false; graphics.fademode = FADE_NONE; ed.keydelay = 6; ed.settingskey = true; ed.old_note_timer = 0; ed.note_timer = 0; ed.roomnamehide = 0; // Might've been changed in a script font::set_level_font(cl.level_font_name.c_str()); DEFER_CALLBACK(resetbg); music.fadeout(); //If warpdir() is used during playtesting, we need to set it back after! for (int j = 0; j < cl.maxheight; j++) { for (int i = 0; i < cl.maxwidth; i++) { cl.roomproperties[i+(j*cl.maxwidth)].warpdir=ed.kludgewarpdir[i+(j*cl.maxwidth)]; } } graphics.titlebg.scrolldir = 0; graphics.backgrounddrawn = false; graphics.foregrounddrawn = false; } static void returntoingametemp(void) { extern Game game; game.returntomenu(game.kludge_ingametemp); } static void returntoedsettings(void) { extern Game game; game.returntomenu(Menu::ed_settings); } static void nextbgcolor(void) { map.nexttowercolour(); } static void setfademode(void) { graphics.fademode = graphics.ingame_fademode; } static void setflipmode(void) { graphics.flipmode = graphics.setflipmode; } void Game::returntoingame(void) { ingame_titlemode = false; mapheld = true; if (ingame_editormode) { ingame_editormode = false; DEFER_CALLBACK(returntoedsettings); gamestate = EDITORMODE; ed.settingskey = true; } else { DEFER_CALLBACK(returntoingametemp); gamestate = MAPMODE; DEFER_CALLBACK(setflipmode); DEFER_CALLBACK(setfademode); if (!map.custommode && !graphics.setflipmode) { obj.flags[73] = true; } } DEFER_CALLBACK(nextbgcolor); if (nocompetitive()) { invalidate_ndm_trophy(); } } void Game::unlockAchievement(const char* name) { #ifdef MAKEANDPLAY UNUSED(name); #else if (map.custommode) { return; } vlog_debug("Achievement \"%s\" unlocked.", name); NETWORK_unlockAchievement(name); #endif } void Game::mapmenuchange(const enum GameGamestate newgamestate, const bool user_initiated) { if (user_initiated && graphics.resumegamemode) { return; } prevgamestate = gamestate; gamestate = newgamestate; graphics.resumegamemode = false; mapheld = true; gameScreen.recacheTextures(); if (prevgamestate == GAMEMODE) { graphics.menuoffset = 240; } else { graphics.menuoffset = 0; } graphics.oldmenuoffset = graphics.menuoffset; } void Game::copyndmresults(void) { ndmresultcrewrescued = crewrescued(); ndmresulttrinkets = trinkets(); ndmresulthardestroom = hardestroom; ndmresulthardestroom_x = hardestroom_x; ndmresulthardestroom_y = hardestroom_y; ndmresulthardestroom_specialname = hardestroom_specialname; SDL_memcpy(ndmresultcrewstats, crewstats, sizeof(ndmresultcrewstats)); } void Game::invalidate_ndm_trophy(void) { if (nodeatheligible) { vlog_debug("NDM trophy is invalidated!"); } nodeatheligible = false; } static inline int get_framerate(const int slowdown, const int deathseq) { if (deathseq != -1) { return 34; } switch (slowdown) { case 30: return 34; case 24: return 41; case 18: return 55; case 12: return 83; } return 34; } int Game::get_timestep(void) { if ((gamestate == GAMEMODE || (gamestate == TELEPORTERMODE && !useteleporter)) && level_debugger::is_active() && !level_debugger::is_pausing() && key.isDown(SDLK_f)) { return 1; } switch (gamestate) { case GAMEMODE: return get_framerate(slowdown, deathseq); default: return 34; } } bool Game::physics_frozen(void) { return roomname_translator::is_pausing() || level_debugger::is_pausing(); } bool Game::incompetitive(void) { return ( !map.custommode && swnmode && (swngame == SWN_SUPERGRAVITRON || swngame == SWN_START_SUPERGRAVITRON_STEP_1 || swngame == SWN_START_SUPERGRAVITRON_STEP_2) ) || intimetrial || nodeathmode; } bool Game::nocompetitive(void) { return slowdown < 30 || map.invincibility; } bool Game::nocompetitive_unless_translator(void) { return slowdown < 30 || (map.invincibility && !roomname_translator::enabled); } void Game::sabotage_time_trial(void) { timetrialcheater = true; hours++; deathcounts += 100; timetrialparlost = true; } bool Game::isingamecompletescreen(void) { return (state >= 3501 && state <= 3518) || (state >= 3520 && state <= 3522); }