text: #define GAME_DEFINITION
  #include "Game.h"
  
  #include <sstream>
  #include <stdlib.h>
  #include <string.h>
  #include <tinyxml2.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 "KeyPoll.h"
  #include "MakeAndPlay.h"
  #include "Map.h"
  #include "Music.h"
  #include "Network.h"
  #include "Screen.h"
  #include "Script.h"
  #include "UtilityClass.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;
  }
  
  static const char* get_summary(
      const char* filename,
      const char* savename,
      tinyxml2::XMLDocument& doc
  ) {
      tinyxml2::XMLHandle hDoc(&doc);
      tinyxml2::XMLElement* pElem;
      bool success;
      const char* retval = "";
  
      success = FILESYSTEM_loadTiXml2Document(filename, doc);
      if (!success)
      {
          vlog_info("%s not found", savename);
          goto end;
      }
  
      if (doc.Error())
      {
          vlog_error("Error parsing %s: %s", savename, doc.ErrorStr());
          goto end;
      }
  
      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, "summary") == 0)
          {
              retval = pText;
          }
      }
  
  end:
      return retval;
  }
  
  void Game::init(void)
  {
      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_x = 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;
  
      customcol=0;
  
      SDL_memset(crewstats, false, sizeof(crewstats));
      SDL_memset(ndmresultcrewstats, false, sizeof(ndmresultcrewstats));
      SDL_memset(tele_crewstats, false, sizeof(tele_crewstats));
      SDL_memset(quick_crewstats, false, sizeof(quick_crewstats));
      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;
  
      tele_gametime = "00:00";
      tele_trinkets = 0;
      tele_currentarea = "Error! Error!";
      quick_gametime = "00:00";
      quick_trinkets = 0;
      quick_currentarea = "Error! Error!";
  
      //Menu stuff initiliased here:
      SDL_memset(unlock, false, sizeof(unlock));
      SDL_memset(unlocknotify, false, sizeof(unlock));
  
      currentmenuoption = 0;
      current_credits_list_index = 0;
      menuxoff = 0;
      menuyoff = 0;
      menucountdown = 0;
      levelpage=0;
      playcustomlevel=0;
      createmenu(Menu::mainmenu);
  
      silence_settings_error = false;
  
      deathcounts = 0;
      gameoverdelay = 0;
      resetgameclock();
      gamesaved = false;
      gamesavefailed = false;
      savetime = "00:00";
      savearea = "nowhere";
      savetrinkets = 0;
  
      intimetrial = false;
      timetrialcountdown = 0;
      timetrialshinytarget = 0;
      timetrialparlost = false;
      timetrialpar = 0;
      timetrialresulttime = 0;
      timetrialresultframes = 0;
      timetrialresultshinytarget = 0;
      timetrialresulttrinkets = 0;
      timetrialresultpar = 0;
      timetrialresultdeaths = 0;
  
      totalflips = 0;
      hardestroom = "Welcome Aboard";
      hardestroomdeaths = 0;
      currentroomdeaths=0;
  
      inertia = 1.1f;
      swnmode = false;
      swntimer = 0;
      swngame = 0;//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;
      quicksummary = get_summary("saves/qsave.vvv", "qsave.vvv", doc);
  
  
      tinyxml2::XMLDocument docTele;
      telesummary = get_summary("saves/tsave.vvv", "tsave.vvv", doc);
  
      screenshake = flashlight = 0 ;
  
      stat_trinkets = 0;
  
      state = 1;
      statedelay = 0;
      //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;
  #if !defined(NO_CUSTOM_LEVELS) && !defined(NO_EDITOR)
      ingame_editormode = false;
  #endif
      kludge_ingametemp = Menu::mainmenu;
      slidermode = SLIDER_NONE;
  
      disablepause = false;
      disableaudiopause = false;
      disabletemporaryaudiopause = true;
      inputdelay = false;
  }
  
  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)
          {
              obj.entities[i].invis = false;
          }
      }
  }
  
  void Game::clearcustomlevelstats(void)
  {
      //just clearing the array
      customlevelstats.clear();
  }
  
  
  void Game::updatecustomlevelstats(std::string clevel, int cscore)
  {
      if (clevel.find("levels/") != std::string::npos)
      {
          clevel = clevel.substr(7);
      }
      int tvar=-1;
      for(size_t j=0; j<customlevelstats.size(); j++)
      {
          if(clevel==customlevelstats[j].name)
          {
              tvar=j;
              break;
          }
      }
      if(tvar>=0)
      {
          // We have an existing entry
          // Don't update it unless it's a higher score
          if (cscore > customlevelstats[tvar].score)
          {
              customlevelstats[tvar].score=cscore;
          }
      }
      else
      {
          //add a new entry
          CustomLevelStat levelstat = {clevel, cscore};
          customlevelstats.push_back(levelstat);
      }
      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;
      }
  
      // Old system
      std::vector<std::string> customlevelnames;
      std::vector<int> customlevelscores;
  
      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)
          {
              for (tinyxml2::XMLElement* stat_el = pElem->FirstChildElement(); stat_el; stat_el = stat_el->NextSiblingElement())
              {
                  CustomLevelStat stat = {};
  
                  if (stat_el->GetText() != NULL)
                  {
                      stat.score = help.Int(stat_el->GetText());
                  }
  
                  if (stat_el->Attribute("name"))
                  {
                      stat.name = stat_el->Attribute("name");
                  }
  
                  customlevelstats.push_back(stat);
              }
  
              return;
          }
      }
  
  
      // Since we're still here, we must be on the old system
      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++)
      {
          CustomLevelStat stat = {customlevelnames[i], customlevelscores[i]};
          customlevelstats.push_back(stat);
      }
  }
  
  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;
      for(int i = 0; i < numcustomlevelstats; i++ )
      {
          customlevelscorestr += help.String(customlevelstats[i].score) + ",";
      }
      xml::update_tag(msgs, "customlevelscore", customlevelscorestr.c_str());
  
      std::string customlevelstatsstr;
      for(int i = 0; i < numcustomlevelstats; i++ )
      {
          customlevelstatsstr += customlevelstats[i].name + "|";
      }
      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 (size_t i = 0; i < customlevelstats.size(); i++)
      {
          stat_el = doc.NewElement("stat");
          CustomLevelStat& stat = customlevelstats[i];
  
          stat_el->SetAttribute("name", stat.name.c_str());
          stat_el->LinkEndChild(doc.NewText(help.String(stat.score).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, 165, 165, 255);
      graphics.addline("                                   ");
      graphics.addline("");
      graphics.addline("");
      graphics.textboxcenterx();
  }
  
  void Game::crewmate_textbox(const int r, const int g, const int b)
  {
      graphics.createtextboxflipme("", -1, 64 + 8 + 16, r, g, b);
      graphics.addline("     You have rescued  ");
      graphics.addline("      a crew member!   ");
      graphics.addline("");
      graphics.textboxcenterx();
  }
  
  void Game::remaining_textbox(void)
  {
      const int remaining = 6 - crewrescued();
      const char* string;
      char buffer[SCREEN_WIDTH_CHARS + 1];
      if (remaining == 1)
      {
          string = "  One remains  ";
      }
      else if (remaining > 0)
      {
          SDL_snprintf(
              buffer,
              sizeof(buffer),
              "  %s remain  ",
              help.number_words(remaining).c_str()
          );
          string = buffer;
      }
      else
      {
          string = "  All Crew Members Rescued!  ";
      }
  
      graphics.createtextboxflipme(string, -1, 128 + 16, 174, 174, 174);
      graphics.textboxcenterx();
  }
  
  void Game::actionprompt_textbox(void)
  {
      graphics.createtextboxflipme(" Press ACTION to continue ", -1, 196, 164, 164, 255);
      graphics.textboxcenterx();
  }
  
  void Game::savetele_textbox(void)
  {
      if (inspecial() || map.custommode)
      {
          return;
      }
  
      if (savetele())
      {
          graphics.createtextboxflipme("    Game Saved    ", -1, 12, 174, 174, 174);
          graphics.textboxtimer(25);
      }
      else
      {
          graphics.createtextboxflipme("  ERROR: Could not save game!  ", -1, 12, 255, 60, 60);
          graphics.textboxtimer(50);
      }
  }
  
  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
              {
                  /* Prevent softlocks if there's no cutscene running right now */
                  hascontrol = true;
                  completestop = false;
              }
              break;
          case 1:
              //Game initilisation
              state = 0;
              break;
          case 2:
              //Opening cutscene
              advancetext = true;
              hascontrol = false;
              state = 3;
              graphics.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
              graphics.addline("intro to story!");
              //Oh no! what happen to rest of crew etc crash into dimension
              break;
          case 4:
              //End of opening cutscene for now
              graphics.createtextbox("  Press arrow keys or WASD to move  ", -1, 195, 174, 174, 174);
              graphics.textboxtimer(60);
              state = 0;
              break;
          case 5:
              //Demo over
              advancetext = true;
              hascontrol = false;
  
              startscript = true;
              newscript="returntohub";
              obj.removetrigger(5);
              state = 6;
              break;
          case 7:
              //End of opening cutscene for now
              graphics.textboxremove();
              hascontrol = true;
              advancetext = false;
              state = 0;
              break;
          case 8:
              //Enter dialogue
              obj.removetrigger(8);
              if (!obj.flags[13])
              {
                  obj.flags[13] = true;
                  graphics.createtextbox("  Press ENTER to view map  ", -1, 155, 174, 174, 174);
                  graphics.addline("      and quicksave");
                  graphics.textboxtimer(60);
              }
              state = 0;
              break;
  
          case 9:
              if (!map.custommode && nocompetitive())
              {
                  returntolab();
  
                  startscript = true;
                  newscript = "disableaccessibility";
  
                  state = 0;
                  break;
              }
  
              //Start SWN Minigame Mode B
              obj.removetrigger(9);
  
              swnmode = true;
              swngame = 6;
              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;
  
              state = 0;
              break;
  
          case 10:
              //Start SWN Minigame Mode A
              obj.removetrigger(10);
  
              swnmode = true;
              swngame = 4;
              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;
  
              state = 0;
              break;
  
          case 11:
              //Intermission 1 instructional textbox, depends on last saved
              graphics.textboxremovefast();
              graphics.createtextbox("   When you're NOT standing on   ", -1, 3, 174, 174, 174);
              if (graphics.flipmode)
              {
                  if (lastsaved == 2)
                  {
                      graphics.addline("   the ceiling, Vitellary will");
                  }
                  else if (lastsaved == 3)
                  {
                      graphics.addline("   the ceiling, Vermilion will");
                  }
                  else if (lastsaved == 4)
                  {
                      graphics.addline("   the ceiling, Verdigris will");
                  }
                  else if (lastsaved == 5)
                  {
                      graphics.addline("   the ceiling, Victoria will");
                  }
              }
              else
              {
                  if (lastsaved == 2)
                  {
                      graphics.addline("    the floor, Vitellary will");
                  }
                  else if (lastsaved == 3)
                  {
                      graphics.addline("    the floor, Vermilion will");
                  }
                  else if (lastsaved == 4)
                  {
                      graphics.addline("    the floor, Verdigris will");
                  }
                  else if (lastsaved == 5)
                  {
                      graphics.addline("    the floor, Victoria will");
                  }
              }
  
              graphics.addline("     stop and wait for you.");
              graphics.textboxtimer(180);
              state = 0;
              break;
          case 12:
              //Intermission 1 instructional textbox, depends on last saved
              obj.removetrigger(12);
              if (!obj.flags[61])
              {
                  obj.flags[61] = true;
                  graphics.textboxremovefast();
                  graphics.createtextbox("  You can't continue to the next   ", -1, 8, 174, 174, 174);
                  if (lastsaved == 5)
                  {
                      graphics.addline("  room until she is safely across. ");
                  }
                  else
                  {
                      graphics.addline("  room until he is safely across.  ");
                  }
                  graphics.textboxtimer(120);
              }
              state = 0;
              break;
          case 13:
              //textbox removal
              obj.removetrigger(13);
              graphics.textboxremovefast();
              state = 0;
              break;
          case 14:
              //Intermission 1 instructional textbox, depends on last saved
              if (graphics.flipmode)
              {
                  graphics.createtextbox(" When you're standing on the ceiling, ", -1, 3, 174, 174, 174);
              }
              else
              {
                  graphics.createtextbox(" When you're standing on the floor, ", -1, 3, 174, 174, 174);
              }
              if (lastsaved == 2)
              {
                  graphics.addline(" Vitellary will try to walk to you. ");
              }
              else if (lastsaved == 3)
              {
                  graphics.addline(" Vermilion will try to walk to you. ");
              }
              else if (lastsaved == 4)
              {
                  graphics.addline(" Verdigris will try to walk to you. ");
              }
              else if (lastsaved == 5)
              {
                  graphics.addline(" Victoria will try to walk to you. ");
              }
              graphics.textboxtimer(280);
  
              state = 0;
              break;
  
          case 15:
          {
              //leaving the naughty corner
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[obj.getplayer()].tile = 0;
              }
              state = 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(2);
              }
              state = 0;
              break;
          }
  
          case 17:
              //Arrow key tutorial
              obj.removetrigger(17);
              graphics.createtextbox(" If you prefer, you can press UP or ", -1, 195, 174, 174, 174);
              graphics.addline("   DOWN instead of ACTION to flip.");
              graphics.textboxtimer(100);
              state = 0;
              break;
  
          case 20:
              if (!obj.flags[1])
              {
                  obj.flags[1] = true;
                  state = 0;
                  graphics.textboxremove();
              }
              obj.removetrigger(20);
              break;
          case 21:
              if (!obj.flags[2])
              {
                  obj.flags[2] = true;
                  state = 0;
                  graphics.textboxremove();
              }
              obj.removetrigger(21);
              break;
          case 22:
              if (!obj.flags[3])
              {
                  graphics.textboxremovefast();
                  obj.flags[3] = true;
                  state = 0;
                  graphics.createtextbox("  Press ACTION to flip  ", -1, 25, 174, 174, 174);
                  graphics.textboxtimer(60);
              }
              obj.removetrigger(22);
              break;
  
          case 30:
              //Generic "run script"
              if (!obj.flags[4])
              {
                  obj.flags[4] = true;
                  startscript = true;
                  newscript="firststeps";
                  state = 0;
              }
              obj.removetrigger(30);
              state = 0;
              break;
          case 31:
              //state = 55;  statedelay = 50;
              state = 0;
              statedelay = 0;
              if (!obj.flags[6])
              {
                  obj.flags[6] = true;
  
                  obj.flags[5] = true;
                  startscript = true;
                  newscript="communicationstation";
                  state = 0;
                  statedelay = 0;
              }
              obj.removetrigger(31);
              break;
          case 32:
              //Generic "run script"
              if (!obj.flags[7])
              {
                  obj.flags[7] = true;
                  startscript = true;
                  newscript="teleporterback";
                  state = 0;
              }
              obj.removetrigger(32);
              state = 0;
              break;
          case 33:
              //Generic "run script"
              if (!obj.flags[9])
              {
                  obj.flags[9] = true;
                  startscript = true;
                  newscript="rescueblue";
                  state = 0;
              }
              obj.removetrigger(33);
              state = 0;
              break;
          case 34:
              //Generic "run script"
              if (!obj.flags[10])
              {
                  obj.flags[10] = true;
                  startscript = true;
                  newscript="rescueyellow";
                  state = 0;
              }
              obj.removetrigger(34);
              state = 0;
              break;
          case 35:
              //Generic "run script"
              if (!obj.flags[11])
              {
                  obj.flags[11] = true;
                  startscript = true;
                  newscript="rescuegreen";
                  state = 0;
              }
              obj.removetrigger(35);
              state = 0;
              break;
          case 36:
              //Generic "run script"
              if (!obj.flags[8])
              {
                  obj.flags[8] = true;
                  startscript = true;
                  newscript="rescuered";
                  state = 0;
              }
              obj.removetrigger(36);
              state = 0;
              break;
  
          case 37:
              //Generic "run script"
              if (companion == 0)
              {
                  startscript = true;
                  newscript="int2_yellow";
                  state = 0;
              }
              obj.removetrigger(37);
              state = 0;
              break;
          case 38:
              //Generic "run script"
              if (companion == 0)
              {
                  startscript = true;
                  newscript="int2_red";
                  state = 0;
              }
              obj.removetrigger(38);
              state = 0;
              break;
          case 39:
              //Generic "run script"
              if (companion == 0)
              {
                  startscript = true;
                  newscript="int2_green";
                  state = 0;
              }
              obj.removetrigger(39);
              state = 0;
              break;
          case 40:
              //Generic "run script"
              if (companion == 0)
              {
                  startscript = true;
                  newscript="int2_blue";
                  state = 0;
              }
              obj.removetrigger(40);
              state = 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";
                  }
                  state = 0;
              }
              obj.removetrigger(41);
              state = 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";
                  }
                  state = 0;
              }
              obj.removetrigger(42);
              state = 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";
                  }
                  state = 0;
              }
              obj.removetrigger(43);
              state = 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";
                  }
                  state = 0;
              }
              obj.removetrigger(44);
              state = 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";
                  }
                  state = 0;
              }
              obj.removetrigger(45);
              state = 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";
                  }
                  state = 0;
              }
              obj.removetrigger(46);
              state = 0;
              break;
  
          case 47:
              //Generic "run script"
              if (!obj.flags[69])
              {
                  obj.flags[69] = true;
                  startscript = true;
                  newscript="trenchwarfare";
                  state = 0;
              }
              obj.removetrigger(47);
              state = 0;
              break;
          case 48:
              //Generic "run script"
              if (!obj.flags[70])
              {
                  obj.flags[70] = true;
                  startscript = true;
                  newscript="trinketcollector";
                  state = 0;
              }
              obj.removetrigger(48);
              state = 0;
              break;
          case 49:
              //Start final level music
              if (!obj.flags[71])
              {
                  obj.flags[71] = true;
                  music.niceplay(15);  //Final level remix
                  state = 0;
              }
              obj.removetrigger(49);
              state = 0;
              break;
  
          case 50:
              music.playef(15);
              graphics.createtextbox("Help! Can anyone hear", 35, 15, 255, 134, 255);
              graphics.addline("this message?");
              graphics.textboxtimer(60);
              state++;
              statedelay = 100;
              break;
          case 51:
              music.playef(15);
              graphics.createtextbox("Verdigris? Are you out", 30, 12, 255, 134, 255);
              graphics.addline("there? Are you ok?");
              graphics.textboxtimer(60);
              state++;
              statedelay = 100;
              break;
          case 52:
              music.playef(15);
              graphics.createtextbox("Please help us! We've crashed", 5, 22, 255, 134, 255);
              graphics.addline("and need assistance!");
              graphics.textboxtimer(60);
              state++;
              statedelay = 100;
              break;
          case 53:
              music.playef(15);
              graphics.createtextbox("Hello? Anyone out there?", 40, 15, 255, 134, 255);
              graphics.textboxtimer(60);
              state++;
              statedelay = 100;
              break;
          case 54:
              music.playef(15);
              graphics.createtextbox("This is Doctor Violet from the", 5, 8, 255, 134, 255);
              graphics.addline("D.S.S. Souleye! Please respond!");
              graphics.textboxtimer(60);
              state++;
              statedelay = 100;
              break;
          case 55:
              music.playef(15);
              graphics.createtextbox("Please... Anyone...", 45, 14, 255, 134, 255);
              graphics.textboxtimer(60);
              state++;
              statedelay = 100;
              break;
          case 56:
              music.playef(15);
              graphics.createtextbox("Please be alright, everyone...", 25, 18, 255, 134, 255);
              graphics.textboxtimer(60);
              state=50;
              statedelay = 100;
              break;
  
  
          case 80:
              //Used to return to menu from the game
              if(graphics.fademode == 1)    state++;
              break;
          case 81:
              quittomenu();
              music.play(6); //should be after quittomenu()
              state = 0;
              break;
  
          case 82:
              //Time Trial Complete!
              obj.removetrigger(82);
              hascontrol = false;
  
              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){
                      if(timetriallevel==0) unlockAchievement("vvvvvvtimetrial_station1_fixed");
                      if(timetriallevel==1) unlockAchievement("vvvvvvtimetrial_lab_fixed");
                      if(timetriallevel==2) unlockAchievement("vvvvvvtimetrial_tower_fixed");
                      if(timetriallevel==3) unlockAchievement("vvvvvvtimetrial_station2_fixed");
                      if(timetriallevel==4) unlockAchievement("vvvvvvtimetrial_warp_fixed");
                      if(timetriallevel==5) unlockAchievement("vvvvvvtimetrial_final_fixed");
                  }
              }
  
              savestatsandsettings();
  
              graphics.fademode = 2;
              music.fadeout();
              state++;
              break;
          case 83:
              frames--;
              if(graphics.fademode == 1)    state++;
              break;
          case 84:
              quittomenu();
              createmenu(Menu::timetrialcomplete);
              state = 0;
              break;
  
  
          case 85:
              //Cutscene skip version of final level change
              obj.removetrigger(85);
              //Init final stretch
              state++;
              music.playef(9);
              music.play(2);
              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;
  
              state = 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);
              state = 0;
              break;
          case 91:
              //Generic "run script"
              startscript = true;
              newscript="startexpolevel_lab";
              obj.removetrigger(91);
              state = 0;
              break;
          case 92:
              //Generic "run script"
              startscript = true;
              newscript="startexpolevel_warp";
              obj.removetrigger(92);
              state = 0;
              break;
          case 93:
              //Generic "run script"
              startscript = true;
              newscript="startexpolevel_tower";
              obj.removetrigger(93);
              state = 0;
              break;
          case 94:
              //Generic "run script"
              startscript = true;
              newscript="startexpolevel_station2";
              obj.removetrigger(94);
              state = 0;
              break;
          case 95:
              //Generic "run script"
              startscript = true;
              newscript="startexpolevel_final";
              obj.removetrigger(95);
              state = 0;
              break;
  
          case 96:
              //Used to return to gravitron to game
              if(graphics.fademode == 1)    state++;
              break;
          case 97:
              returntolab();
              state = 0;
              break;
  
          case 100:
              //
              //                       Meeting crewmate in the warpzone
              //
              obj.removetrigger(100);
              if (!obj.flags[4])
              {
                  obj.flags[4] = true;
                  state++;
              }
              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(1);
              }
              if (INBOUNDS_VEC(i, obj.entities) && obj.entities[i].onground > 0)
              {
                  state++;
              }
          }
          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);
              state++;
              music.playef(12);
          }
          break;
          case 104:
              graphics.createtextbox("I'm glad you're ok!", 135, 152, 164, 164, 255);
              state++;
              music.playef(11);
              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...");
              state++;
              music.playef(2);
              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, 164, 164, 255);
              graphics.addline("teleporter key!");
              state++;
              music.playef(11);
              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, 164, 164, 255);
              state++;
              music.playef(11);
              graphics.textboxactive();
  
          }
          break;
          case 112:
              graphics.textboxremove();
              hascontrol = true;
              advancetext = false;
  
              state = 0;
              break;
  
          case 115:
              //
              //                       Test script for space station, totally delete me!
              //
              hascontrol = false;
              state++;
          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.textboxcenterx();
              state++;
              break;
          case 118:
              graphics.textboxremove();
              hascontrol = true;
              advancetext = false;
  
              state = 0;
              break;
  
          case 120:
              //
              //                       Meeting crewmate in the space station
              //
              obj.removetrigger(120);
              if (!obj.flags[5])
              {
                  obj.flags[5] = true;
                  state++;
              }
              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(1);
              }
              if (INBOUNDS_VEC(i, obj.entities) && obj.entities[i].onroof > 0)
              {
                  state++;
              }
  
          }
          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, 255, 255, 134);
              state++;
              music.playef(14);
              break;
          }
          case 124:
          {
              graphics.createtextbox("I've found a teleporter, but", 60-20, 90 - 40, 255, 255, 134);
              graphics.addline("I can't get it to go anywhere...");
              state++;
              music.playef(2);
              graphics.textboxactive();
              break;
          }
          case 126:
              graphics.createtextbox("I can help with that!", 125, 152-40, 164, 164, 255);
              state++;
              music.playef(11);
              graphics.textboxactive();
              break;
          case 128:
              graphics.createtextbox("I have the teleporter", 130, 152-35, 164, 164, 255);
              graphics.addline("codex for our ship!");
              state++;
              music.playef(11);
              graphics.textboxactive();
              break;
  
          case 130:
          {
              graphics.createtextbox("Yey! Let's go home!", 60-30, 90-35, 255, 255, 134);
              state++;
              music.playef(14);
              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;
  
              state = 0;
              break;
  
          case 200:
              //Init final stretch
              state++;
              music.playef(9);
              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";
              state = 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);
              state = 0;
              break;
  
          case 1000:
              graphics.showcutscenebars = true;
              hascontrol = false;
              completestop = true;
              state++;
              statedelay = 15;
              break;
          case 1001:
              //Found a trinket!
              advancetext = true;
              state++;
              graphics.createtextboxflipme("        Congratulations!       ", 50, 85, 174, 174, 174);
              graphics.addline("");
              graphics.addline("You have found a shiny trinket!");
              graphics.textboxcenterx();
  
  #if !defined(NO_CUSTOM_LEVELS)
              if(map.custommode)
              {
                  graphics.createtextboxflipme(" " + help.number_words(trinkets()) + " out of " + help.number_words(cl.numtrinkets())+ " ", 50, 135, 174, 174, 174);
                  graphics.textboxcenterx();
              }
              else
  #endif
              {
                  graphics.createtextboxflipme(" " + help.number_words(trinkets()) + " out of Twenty ", 50, 135, 174, 174, 174);
                  graphics.textboxcenterx();
              }
              break;
          case 1002:
              if (!advancetext)
              {
                  // Prevent softlocks if we somehow don't have advancetext
                  state++;
              }
              break;
          case 1003:
              graphics.textboxremove();
              hascontrol = true;
              advancetext = false;
              completestop = false;
              state = 0;
              if (music.currentsong > -1)
              {
                  music.fadeMusicVolumeIn(3000);
              }
              graphics.showcutscenebars = false;
              break;
  
          case 1010:
              graphics.showcutscenebars = true;
              hascontrol = false;
              completestop = true;
              state++;
              statedelay = 15;
              break;
  #if !defined(NO_CUSTOM_LEVELS)
          case 1011:
              //Found a crewmate!
              advancetext = true;
              state++;
              graphics.createtextboxflipme("        Congratulations!       ", 50, 85, 174, 174, 174);
              graphics.addline("");
              graphics.addline("You have found a lost crewmate!");
              graphics.textboxcenterx();
  
              if(cl.numcrewmates()-crewmates()==0)
              {
                  graphics.createtextboxflipme("     All crewmates rescued!    ", 50, 135, 174, 174, 174);
              }
              else if(cl.numcrewmates()-crewmates()==1)
              {
                  graphics.createtextboxflipme("    " + help.number_words(cl.numcrewmates()-crewmates())+ " remains    ", 50, 135, 174, 174, 174);
              }
              else
              {
                  graphics.createtextboxflipme("     " + help.number_words(cl.numcrewmates()-crewmates())+ " remain    ", 50, 135, 174, 174, 174);
              }
              graphics.textboxcenterx();
              break;
          case 1012:
              if (!advancetext)
              {
                  // Prevent softlocks if we somehow don't have advancetext
                  state++;
              }
              break;
          case 1013:
              graphics.textboxremove();
              hascontrol = true;
              advancetext = false;
              completestop = false;
              state = 0;
  
              if(cl.numcrewmates()-crewmates()==0)
              {
                  if(map.custommodeforreal)
                  {
                      graphics.fademode = 2;
                      state=1014;
                  }
  #ifndef NO_EDITOR
                  else
                  {
                      returntoeditor();
                  }
  #endif
              }
              else
              {
                  if (cl.levmusic > 0)
                  {
                      music.fadeMusicVolumeIn(3000);
                  }
              }
              graphics.showcutscenebars = false;
              break;
  #endif
          case 1014:
              frames--;
              if(graphics.fademode == 1)    state++;
              break;
          case 1015:
  #if !defined(NO_CUSTOM_LEVELS)
              //Update level stats
              if(cl.numcrewmates()-crewmates()==0)
              {
                  //Finished level
                  if (trinkets() >= cl.numtrinkets())
                  {
                      //and got all the trinkets!
                      updatecustomlevelstats(customlevelfilename, 3);
                  }
                  else
                  {
                      updatecustomlevelstats(customlevelfilename, 1);
                  }
              }
  #endif
              quittomenu();
              music.play(6); //should be after quittomenu()
              state = 0;
              break;
  
  
          case 2000:
              //Game Saved!
              savetele_textbox();
              state = 0;
              break;
  
          case 2500:
  
              music.play(5);
              //Activating a teleporter (appear)
              state++;
              statedelay = 15;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          case 2501:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              //we're done here!
              music.playef(10);
              break;
          case 2502:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 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:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 10;
              }
              break;
          }
          case 2504:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  //obj.entities[i].xp += 10;
              }
              break;
          }
          case 2505:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 8;
              }
              break;
          }
          case 2506:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 6;
              }
              break;
          }
          case 2507:
          {
              state++;
              break;
          }
          case 2508:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 2;
              }
              break;
          }
          case 2509:
          {
              state++;
              statedelay = 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, 164, 164, 255);
              state++;
              music.playef(11);
              graphics.textboxactive();
              break;
          case 2512:
              advancetext = true;
              hascontrol = false;
              graphics.createtextbox("Is anyone there?", 125+8, 152-24, 164, 164, 255);
              state++;
              music.playef(11);
              graphics.textboxactive();
              break;
          case 2514:
              graphics.textboxremove();
              hascontrol = true;
              advancetext = false;
  
              state = 0;
              music.play(3);
              break;
  
  
          case 3000:
              //Activating a teleporter (long version for level complete)
              state++;
              statedelay = 30;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          case 3001:
              //Activating a teleporter 2
              state++;
              statedelay = 15;
              flashlight = 5;
              music.playef(9);
              break;
          case 3002:
              //Activating a teleporter 2
              state++;
              statedelay = 15;
              flashlight = 5;
              music.playef(9);
              break;
          case 3003:
              //Activating a teleporter 2
              state++;
              statedelay = 15;
              flashlight = 5;
              music.playef(9);
              break;
          case 3004:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              //we're done here!
              music.playef(10);
              break;
          case 3005:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 50;
              switch(companion)
              {
              case 6:
                  state = 3006;
                  break; //Warp Zone
              case 7:
                  state = 3020;
                  break; //Space Station
              case 8:
                  state = 3040;
                  break; //Lab
              case 9:
                  state = 3060;
                  break; //Tower
              case 10:
                  state = 3080;
                  break; //Intermission 2
              case 11:
                  state = 3085;
                  break; //Intermission 1
              }
  
              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(4);
              lastsaved = 4;
              music.play(0);
              state++;
              statedelay = 75;
  
              levelcomplete_textbox();
              break;
          case 3007:
              state++;
              statedelay = 45;
  
              crewmate_textbox(175, 174, 174);
              break;
          case 3008:
              state++;
              statedelay = 45;
  
              remaining_textbox();
              break;
          case 3009:
              state++;
              statedelay = 0;
  
              actionprompt_textbox();
              break;
          case 3010:
              if (jumppressed)
              {
                  state++;
                  statedelay = 30;
                  graphics.textboxremove();
              }
              break;
          case 3011:
              state = 3070;
              statedelay = 0;
              break;
  
          case 3020:
              //Level complete! (Space Station 2)
              unlocknum(3);
              lastsaved = 2;
              music.play(0);
              state++;
              statedelay = 75;
  
  
              levelcomplete_textbox();
              break;
          case 3021:
              state++;
              statedelay = 45;
  
              crewmate_textbox(174, 175, 174);
              break;
          case 3022:
              state++;
              statedelay = 45;
  
              remaining_textbox();
              break;
          case 3023:
              state++;
              statedelay = 0;
  
              actionprompt_textbox();
              break;
          case 3024:
              if (jumppressed)
              {
                  state++;
                  statedelay = 30;
                  graphics.textboxremove();
              }
              break;
          case 3025:
              state = 3070;
              statedelay = 0;
              break;
  
          case 3040:
              //Level complete! (Lab)
              unlocknum(1);
              lastsaved = 5;
              music.play(0);
              state++;
              statedelay = 75;
  
              levelcomplete_textbox();
              break;
          case 3041:
              state++;
              statedelay = 45;
  
              crewmate_textbox(174, 174, 175);
              break;
          case 3042:
              state++;
              statedelay = 45;
  
              remaining_textbox();
              break;
          case 3043:
              state++;
              statedelay = 0;
  
              actionprompt_textbox();
              break;
          case 3044:
              if (jumppressed)
              {
                  state++;
                  statedelay = 30;
                  graphics.textboxremove();
              }
              break;
          case 3045:
              state = 3070;
              statedelay = 0;
              break;
  
          case 3050:
              //Level complete! (Space Station 1)
              unlocknum(0);
              lastsaved = 1;
              music.play(0);
              state++;
              statedelay = 75;
  
  
              levelcomplete_textbox();
              break;
          case 3051:
              state++;
              statedelay = 45;
  
              crewmate_textbox(175, 175, 174);
              break;
          case 3052:
              state++;
              statedelay = 45;
  
              remaining_textbox();
              break;
          case 3053:
              state++;
              statedelay = 0;
  
              actionprompt_textbox();
              break;
          case 3054:
              if (jumppressed)
              {
                  state++;
                  statedelay = 30;
                  graphics.textboxremove();
                  teleportscript = "";
              }
              break;
          case 3055:
              graphics.fademode = 2;
              state++;
              statedelay = 10;
              break;
          case 3056:
              if(graphics.fademode==1)
              {
                  startscript = true;
                  if (crewrescued() == 6)
                  {
                      newscript = "startlevel_final";
                  }
                  else
                  {
                      if (nocutscenes)
                      {
                          newscript="bigopenworldskip";
                      }
                      else
                      {
                          newscript = "bigopenworld";
                      }
                  }
                  state = 0;
              }
              break;
  
  
          case 3060:
              //Level complete! (Tower)
              unlocknum(2);
              lastsaved = 3;
              music.play(0);
              state++;
              statedelay = 75;
  
              levelcomplete_textbox();
              break;
          case 3061:
              state++;
              statedelay = 45;
  
              crewmate_textbox(175, 174, 175);
              break;
          case 3062:
              state++;
              statedelay = 45;
  
              remaining_textbox();
              break;
          case 3063:
              state++;
              statedelay = 0;
  
              actionprompt_textbox();
              break;
          case 3064:
              if (jumppressed)
              {
                  state++;
                  statedelay = 30;
                  graphics.textboxremove();
              }
              break;
          case 3065:
              state = 3070;
              statedelay = 0;
              break;
  
  
          case 3070:
              graphics.fademode = 2;
              state++;
              break;
          case 3071:
              if (graphics.fademode == 1) state++;
              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";
                  state = 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;
                  state = 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;
                  state = 0;
              }
              else
              {
                  startscript = true;
                  newscript="regularreturn";
                  state = 0;
              }
              break;
  
          case 3080:
              //returning from an intermission, very like 3070
              if (inintermission)
              {
                  graphics.fademode = 2;
                  companion = 0;
                  state=3100;
              }
              else
              {
                  unlocknum(7);
                  graphics.fademode = 2;
                  companion = 0;
                  state++;
              }
              break;
          case 3081:
              if (graphics.fademode == 1) state++;
              break;
          case 3082:
              map.finalmode = false;
              startscript = true;
              newscript="regularreturn";
              state = 0;
              break;
  
          case 3085:
              //returning from an intermission, very like 3070
              //return to menu from here
              if (inintermission)
              {
                  companion = 0;
                  supercrewmate = false;
                  state++;
                  graphics.fademode = 2;
                  music.fadeout();
                  state=3100;
              }
              else
              {
                  unlocknum(6);
                  graphics.fademode = 2;
                  companion = 0;
                  supercrewmate = false;
                  state++;
              }
              break;
          case 3086:
              if (graphics.fademode == 1) state++;
              break;
          case 3087:
              map.finalmode = false;
              startscript = true;
              newscript="regularreturn";
              state = 0;
              break;
  
          case 3100:
              if(graphics.fademode == 1)    state++;
              break;
          case 3101:
              quittomenu();
              music.play(6); //should be after quittomenu();
              state = 0;
              break;
  
          case 3500:
              music.fadeout();
              state++;
              statedelay = 120;
              break;
          case 3501:
              //Game complete!
              unlockAchievement("vvvvvvgamecomplete");
              unlocknum(5);
              crewstats[0] = true;
              state++;
              statedelay = 75;
              music.play(7);
  
              graphics.createtextboxflipme("", -1, 12, 164, 165, 255);
              graphics.addline("                                   ");
              graphics.addline("");
              graphics.addline("");
              graphics.textboxcenterx();
              break;
          case 3502:
          {
              state++;
              statedelay = 45+15;
  
              graphics.createtextboxflipme("  All Crew Members Rescued!  ", -1, 64, 0, 0, 0);
              char buffer[SCREEN_WIDTH_CHARS + 1];
              timestringcenti(buffer, sizeof(buffer));
              savetime = buffer;
              break;
          }
          case 3503:
          {
              state++;
              statedelay = 45;
  
              std::string tempstring = help.number_words(trinkets());
              graphics.createtextboxflipme("Trinkets Found:", 48, 84, 0,0,0);
              graphics.createtextboxflipme(tempstring, 180, 84, 0, 0, 0);
              break;
          }
          case 3504:
          {
              state++;
              statedelay = 45+15;
  
              std::string tempstring = savetime;
              graphics.createtextboxflipme("   Game Time:", 64, 96, 0,0,0);
              graphics.createtextboxflipme(tempstring, 180, 96, 0, 0, 0);
              break;
          }
          case 3505:
              state++;
              statedelay = 45;
  
              graphics.createtextboxflipme(" Total Flips:", 64, 123, 0,0,0);
              graphics.createtextboxflipme(help.String(totalflips), 180, 123, 0, 0, 0);
              break;
          case 3506:
              state++;
              statedelay = 45+15;
  
              graphics.createtextboxflipme("Total Deaths:", 64, 135, 0,0,0);
              graphics.createtextboxflipme(help.String(deathcounts), 180, 135, 0, 0, 0);
              break;
          case 3507:
          {
              state++;
              statedelay = 45+15;
  
              std::string tempstring = "Hardest Room (with " + help.String(hardestroomdeaths) + " deaths)";
              graphics.createtextboxflipme(tempstring, -1, 158, 0,0,0);
              graphics.createtextboxflipme(hardestroom, -1, 170, 0, 0, 0);
              break;
          }
          case 3508:
              state++;
              statedelay = 0;
  
              actionprompt_textbox();
              break;
          case 3509:
              if (jumppressed)
              {
                  state++;
                  statedelay = 30;
                  graphics.textboxremove();
              }
              break;
          case 3510:
              //Save stats and stuff here
              if (!obj.flags[73])
              {
                  //flip mode complete
                  unlockAchievement("vvvvvvgamecompleteflip");
                  unlocknum(19);
              }
  
  #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");
              }
          }
  
  
              savestatsandsettings();
              if (nodeathmode)
              {
                  unlockAchievement("vvvvvvmaster"); //bloody hell
                  unlocknum(20);
                  state = 3520;
                  statedelay = 0;
              }
              else
              {
                  statedelay = 120;
                  state++;
              }
              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;
              }
  
              state++;
              statedelay = 30;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          }
          case 3512:
              //Activating a teleporter 2
              state++;
              statedelay = 15;
              flashlight = 5;
              music.playef(9);
              break;
          case 3513:
              //Activating a teleporter 2
              state++;
              statedelay = 15;
              flashlight = 5;
              music.playef(9);
              break;
          case 3514:
              //Activating a teleporter 2
              state++;
              statedelay = 15;
              flashlight = 5;
              music.playef(9);
              break;
          case 3515:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 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(10);
              statedelay = 60;
              break;
          }
          case 3516:
              graphics.fademode = 2;
              state++;
              break;
          case 3517:
              if (graphics.fademode == 1)
              {
                  state++;
                  statedelay = 30;
              }
              break;
          case 3518:
              graphics.fademode = 4;
              state = 0;
              statedelay = 30;
  
              map.finalmode = false;
              map.final_colormode = false;
              map.final_mapcol = 0;
              map.final_colorframe = 0;
              map.finalstretch = 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 = 2;
              state++;
              break;
          case 3521:
              if(graphics.fademode == 1)    state++;
              break;
          case 3522:
              copyndmresults();
              quittomenu();
              createmenu(Menu::nodeathmodecomplete);
              state = 0;
              break;
  
          case 4000:
              //Activating a teleporter (short version)
              state++;
              statedelay = 10;
              flashlight = 5;
              screenshake = 10;
              music.playef(9);
              break;
          case 4001:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              //we're done here!
              music.playef(10);
              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;
              break;
  
          case 4010:
              //Activating a teleporter (default appear)
              state++;
              statedelay = 15;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          case 4011:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              music.playef(10);
              break;
          case 4012:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 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:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 10;
              }
              break;
          }
          case 4014:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 10;
              }
              break;
          }
          case 4015:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 8;
              }
              break;
          }
          case 4016:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 6;
              }
              break;
          }
          case 4017:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 3;
              }
              break;
          }
          case 4018:
          {
              state++;
              statedelay = 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;
              state = 0;
              break;
          }
  
          case 4020:
              //Activating a teleporter (default appear)
              state++;
              statedelay = 15;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          case 4021:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              music.playef(10);
              break;
          case 4022:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 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:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 12;
              }
              break;
          }
          case 4024:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 12;
              }
              break;
          }
          case 4025:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 10;
              }
              break;
          }
          case 4026:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 8;
              }
              break;
          }
          case 4027:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 5;
              }
              break;
          }
          case 4028:
          {
              state++;
              statedelay = 15;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 2;
              }
              break;
          }
          case 4029:
              hascontrol = true;
              advancetext = false;
              state = 0;
              break;
  
          case 4030:
              //Activating a teleporter (default appear)
              state++;
              statedelay = 15;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          case 4031:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              music.playef(10);
              break;
          case 4032:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 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:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 12;
              }
              break;
          }
          case 4034:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 12;
              }
              break;
          }
          case 4035:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 10;
              }
              break;
          }
          case 4036:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 8;
              }
              break;
          }
          case 4037:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 5;
              }
              break;
          }
          case 4038:
          {
              state++;
              statedelay = 15;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 2;
              }
              break;
          }
          case 4039:
              hascontrol = true;
              advancetext = false;
              state = 0;
              break;
  
          case 4040:
              //Activating a teleporter (default appear)
              state++;
              statedelay = 15;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          case 4041:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              music.playef(10);
              break;
          case 4042:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 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:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 12;
                  obj.entities[i].yp -= 15;
              }
              break;
          }
          case 4044:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 12;
                  obj.entities[i].yp -= 10;
              }
              break;
          }
          case 4045:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 12;
                  obj.entities[i].yp -= 10;
              }
              break;
          }
          case 4046:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 8;
                  obj.entities[i].yp -= 8;
              }
              break;
          }
          case 4047:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 6;
                  obj.entities[i].yp -= 8;
              }
              break;
          }
          case 4048:
          {
              state++;
              statedelay = 15;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 3;
              }
              break;
          }
          case 4049:
              hascontrol = true;
              advancetext = false;
              state = 0;
              break;
  
          case 4050:
              //Activating a teleporter (default appear)
              state++;
              statedelay = 15;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          case 4051:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              music.playef(10);
              break;
          case 4052:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 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:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 4;
                  obj.entities[i].yp -= 15;
              }
              break;
          }
          case 4054:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 4;
                  obj.entities[i].yp -= 10;
              }
              break;
          }
          case 4055:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 4;
                  obj.entities[i].yp -= 10;
              }
              break;
          }
          case 4056:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 4;
                  obj.entities[i].yp -= 8;
              }
              break;
          }
          case 4057:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 2;
                  obj.entities[i].yp -= 8;
              }
              break;
          }
          case 4058:
          {
              state++;
              statedelay = 15;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 1;
              }
              break;
          }
          case 4059:
              hascontrol = true;
              advancetext = false;
              state = 0;
              break;
  
          case 4060:
              //Activating a teleporter (default appear)
              state++;
              statedelay = 15;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          case 4061:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              music.playef(10);
              break;
          case 4062:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 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:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 28;
                  obj.entities[i].yp -= 8;
              }
              break;
          }
          case 4064:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 28;
                  obj.entities[i].yp -= 8;
              }
              break;
          }
          case 4065:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 25;
              }
              break;
          }
          case 4066:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 25;
              }
              break;
          }
          case 4067:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 20;
              }
              break;
          }
          case 4068:
          {
              state++;
              statedelay = 15;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp -= 16;
              }
              break;
          }
          case 4069:
              hascontrol = true;
              advancetext = false;
              state = 0;
              break;
  
  
          case 4070:
              //Activating a teleporter (special for final script, player has colour changed to match rescued crewmate)
              state++;
              statedelay = 15;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          case 4071:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              music.playef(10);
              break;
          case 4072:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 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:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 10;
              }
              break;
          }
          case 4074:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 10;
              }
              break;
          }
          case 4075:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 8;
              }
              break;
          }
          case 4076:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 6;
              }
              break;
          }
          case 4077:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 3;
              }
              break;
          }
          case 4078:
          {
              state++;
              statedelay = 15;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 1;
              }
              break;
          }
          case 4079:
              state = 0;
              startscript = true;
              newscript = "finallevel_teleporter";
              break;
  
          case 4080:
              //Activating a teleporter (default appear)
              state++;
              statedelay = 15;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          case 4081:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              music.playef(10);
              break;
          case 4082:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 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:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 10;
              }
              break;
          }
          case 4084:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 10;
              }
              break;
          }
          case 4085:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 8;
              }
              break;
          }
          case 4086:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 6;
              }
              break;
          }
          case 4087:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 3;
              }
              break;
          }
          case 4088:
          {
              state++;
              statedelay = 15;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 1;
              }
              break;
          }
          case 4089:
              startscript = true;
              newscript = "gamecomplete_ending";
              state = 0;
              break;
  
          case 4090:
              //Activating a teleporter (default appear)
              state++;
              statedelay = 15;
              flashlight = 5;
              screenshake = 90;
              music.playef(9);
              break;
          case 4091:
              //Activating a teleporter 2
              state++;
              statedelay = 0;
              flashlight = 5;
              screenshake = 0;
              music.playef(10);
              break;
          case 4092:
          {
              //Activating a teleporter 2
              state++;
              statedelay = 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:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 10;
              }
              break;
          }
          case 4094:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 10;
              }
              break;
          }
          case 4095:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 8;
              }
              break;
          }
          case 4096:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 6;
              }
              break;
          }
          case 4097:
          {
              state++;
              int i = obj.getplayer();
              if (INBOUNDS_VEC(i, obj.entities))
              {
                  obj.entities[i].xp += 3;
              }
              break;
          }
          case 4098:
          {
              state++;
              statedelay = 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";
              }
              state = 0;
              break;
          }
      }
  }
  
  void Game::gethardestroom(void)
  {
      if (currentroomdeaths > hardestroomdeaths)
      {
          hardestroomdeaths = currentroomdeaths;
          hardestroom = map.roomname;
          if (SDL_strcmp(map.roomname, "glitch") == 0)
          {
              if (roomx == 42 && roomy == 51)
              {
                  hardestroom = "Rear Vindow";
              }
              else if (roomx == 48 && roomy == 51)
              {
                  hardestroom = "On the Vaterfront";
              }
              else if (roomx == 49 && roomy == 51)
              {
                  hardestroom = "The Untouchavles";
              }
          }
          else if (SDL_strcmp(map.roomname, "change") == 0)
          {
              if (roomx == 45 && roomy == 51) hardestroom =map.specialnames[3];
              if (roomx == 46 && roomy == 51) hardestroom =map.specialnames[4];
              if (roomx == 47 && roomy == 51) hardestroom =map.specialnames[5];
              if (roomx == 50 && roomy == 53) hardestroom =map.specialnames[6];
              if (roomx == 50 && roomy == 54) hardestroom = map.specialnames[7];
          }
          else if (map.roomname[0] == '\0')
          {
              hardestroom = map.hiddenname;
          }
      }
  }
  
  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 )
  {
  #if !defined(MAKEANDPLAY)
      if (map.custommode)
      {
          //Don't let custom levels unlock things!
          return;
      }
  
      unlock[t] = true;
      savestatsandsettings();
  #endif
  }
  
  void Game::loadstats(struct ScreenSettings* screen_settings)
  {
      tinyxml2::XMLDocument doc;
      tinyxml2::XMLHandle hDoc(&doc);
      tinyxml2::XMLElement* pElem;
      tinyxml2::XMLElement* dataNode;
  
      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)
          {
              screen_settings->scalingMode = help.Int(pText);
          }
  
          if (SDL_strcmp(pKey, "useLinearFilter") == 0)
          {
              screen_settings->linearFilter = 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 (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);
      }
  }
  
  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 = 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_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);
  }
  
  void Game::loadsettings(struct ScreenSettings* screen_settings)
  {
      tinyxml2::XMLDocument doc;
      tinyxml2::XMLHandle hDoc(&doc);
      tinyxml2::XMLElement* dataNode;
  
      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 = 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;
  
      state = 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;
  
      state = 0;
      deathseq = -1;
      lifeseq = 0;
  
      if (!nocutscenes) music.play(5);
  }
  
  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;
          }
          deathcounts++;
          music.playef(2);
          if (INBOUNDS_VEC(i, obj.entities))
          {
              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))
      {
          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;
      state = 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;
  
      state = 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;
      }
  
      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, "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;
          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;
      }
  
      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, "currentsong") == 0)
          {
              int song = help.Int(pText);
              if (song != -1)
              {
                  music.play(song);
              }
          }
          else if (SDL_strcmp(pKey, "showminimap") == 0)
          {
              map.customshowmm = 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);
          }
  
      }
  
      map.showteleporters = true;
      if(obj.flags[12]) map.showtargets = true;
  
  }
  
  struct Summary
  {
      const char* summary;
      int seconds;
      int minutes;
      int hours;
      int savex;
      int savey;
      int trinkets;
      bool finalmode;
      bool crewstats[Game::numcrew];
  };
  
  static void loadthissummary(
      const char* filename,
      struct 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;
      }
  
      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 (pText == NULL)
          {
              pText = "";
          }
  
          if (SDL_strcmp(pKey, "summary") == 0)
          {
              summary->summary = pText;
          }
  
          else 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->savex = help.Int(pText);
          }
          else if (SDL_strcmp(pKey, "savery") == 0)
          {
              summary->savey = help.Int(pText);
          }
          else if (SDL_strcmp(pKey, "trinkets") == 0)
          {
              summary->trinkets = help.Int(pText);
          }
          else if (SDL_strcmp(pKey, "finalmode") == 0)
          {
              map.finalmode = help.Int(pText);
          }
  
          LOAD_ARRAY_RENAME(crewstats, summary->crewstats)
      }
  }
  
  void Game::loadsummary(void)
  {
      tinyxml2::XMLDocument doc;
  
      if (!FILESYSTEM_loadTiXml2Document("saves/tsave.vvv", doc))
      {
          telesummary = "";
      }
      else
      {
          struct Summary summary;
          SDL_zero(summary);
  
          loadthissummary("tsave.vvv", &summary, doc);
  
          telesummary = summary.summary;
          tele_gametime = giventimestring(
              summary.hours,
              summary.minutes,
              summary.seconds
          );
          map.finalmode = summary.finalmode;
          tele_currentarea = map.currentarea(
              map.area(summary.savex, summary.savey)
          );
          SDL_memcpy(tele_crewstats, summary.crewstats, sizeof(tele_crewstats));
          tele_trinkets = summary.trinkets;
      }
  
      if (!FILESYSTEM_loadTiXml2Document("saves/qsave.vvv", doc))
      {
          quicksummary = "";
      }
      else
      {
          struct Summary summary;
          SDL_zero(summary);
  
          loadthissummary("qsave.vvv", &summary, doc);
  
          quicksummary = summary.summary;
          quick_gametime = giventimestring(
              summary.hours,
              summary.minutes,
              summary.seconds
          );
          map.finalmode = summary.finalmode;
          quick_currentarea = map.currentarea(
              map.area(summary.savex, summary.savey)
          );
          SDL_memcpy(quick_crewstats, summary.crewstats, sizeof(quick_crewstats));
          quick_trinkets = summary.trinkets;
      }
  }
  
  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");
      }
  
      telesummary = 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");
      }
  
      quicksummary = 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
  std::string Game::writemaingamesave(tinyxml2::XMLDocument& doc)
  {
      //TODO make this code a bit cleaner.
  
      if (map.custommode || inspecial())
      {
          //Don't trash save data!
          return "";
      }
  
      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);
  
      xml::update_tag(msgs, "trinkets", 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, "finalmode", (int) map.finalmode);
      xml::update_tag(msgs, "finalstretch", (int) map.finalstretch);
  
  
      std::string summary = savearea + ", " + timestring();
      xml::update_tag(msgs, "summary", summary.c_str());
  
      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, "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, "showminimap", (int) map.customshowmm);
  
      xml::update_tag(msgs, "disabletemporaryaudiopause", (int) disabletemporaryaudiopause);
  
      xml::update_tag(msgs, "showtrinkets", (int) map.showtrinkets);
  
      std::string summary = savearea + ", " + timestring();
      xml::update_tag(msgs, "summary", summary.c_str());
  
      customquicksummary = summary;
  
      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
      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();
      }
  
      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();
      }
  }
  
  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)
      {
          //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("play");
  #endif
  #if !defined(NO_CUSTOM_LEVELS)
          option("levels");
  #endif
          option("options");
  #if !defined(MAKEANDPLAY)
          option("credits");
  #endif
          option("quit");
          menuyoff = -10;
          maxspacing = 15;
          break;
  #if !defined(NO_CUSTOM_LEVELS)
      case Menu::playerworlds:
          option("play a level");
   #if !defined(NO_EDITOR)
          option("level editor");
   #endif
          option("open level folder", FILESYSTEM_openDirectoryEnabled());
          option("show level folder path");
          option("back to menu");
          menuyoff = -40;
          maxspacing = 15;
          break;
      case Menu::confirmshowlevelspath:
          option("no, don't show me");
          option("yes, reveal the path");
          menuyoff = -10;
          break;
      case Menu::showlevelspath:
          option("return to levels");
          menuyoff = 60;
          break;
      case Menu::levellist:
          if(cl.ListOfMetaData.size()==0)
          {
              option("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)
                  {
                      //This is, er, suboptimal. Whatever, life optimisation and all that
                      int tvar=-1;
                      for(size_t j=0; j<customlevelstats.size(); j++)
                      {
                          if(cl.ListOfMetaData[i].filename.substr(7) == customlevelstats[j].name)
                          {
                              tvar=j;
                              break;
                          }
                      }
                      const char* prefix;
                      if(tvar>=0)
                      {
                          switch (customlevelstats[tvar].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;
                          }
                      }
                      else
                      {
                          static const char tmp[] = "   ";
                          prefix = tmp;
                      }
                      char text[MENU_TEXT_BYTES];
                      SDL_snprintf(text, sizeof(text), "%s%s", prefix, cl.ListOfMetaData[i].title.c_str());
                      for (size_t ii = 0; text[ii] != '\0'; ++ii)
                      {
                          text[ii] = SDL_tolower(text[ii]);
                      }
                      option(text);
                  }
              }
              if (cl.ListOfMetaData.size() > 8)
              {
                  if((size_t) ((levelpage*8)+8) <cl.ListOfMetaData.size())
                  {
                      option("next page");
                  }
                  else
                  {
                      option("first page");
                  }
                  if (levelpage == 0)
                  {
                      option("last page");
                  }
                  else
                  {
                      option("previous page");
                  }
              }
              option("return to menu");
  
              menuxoff = 20;
              menuyoff = 70-(menuoptions.size()*10);
              menuspacing = 5;
              return; // skip automatic centering, will turn out bad with levels list
          }
          break;
  #endif
      case Menu::quickloadlevel:
          option("continue from save");
          option("start from beginning");
          option("delete save");
          option("back to levels");
          menuyoff = -30;
          break;
      case Menu::deletequicklevel:
          option("no! don't delete");
          option("yes, delete save");
          menuyoff = 64;
          break;
      case Menu::youwannaquit:
          option("yes, quit");
          option("no, return");
          menuyoff = -20;
          break;
      case Menu::errornostart:
          option("ok");
          menuyoff = -20;
          break;
      case Menu::gameplayoptions:
  #if !defined(MAKEANDPLAY)
          if (ingame_titlemode && unlock[18])
  #endif
          {
                  option("flip mode");
          }
          option("toggle fps");
          option("speedrun options");
          option("advanced options");
          option("clear main game data");
          option("clear custom level data");
          option("return");
          menuyoff = -10;
          maxspacing = 15;
          break;
      case Menu::graphicoptions:
          if (!gameScreen.isForcedFullscreen())
          {
              option("toggle fullscreen");
          }
          option("scaling mode");
          if (!gameScreen.isForcedFullscreen())
          {
              option("resize to nearest", gameScreen.isWindowed);
          }
          option("toggle filter");
          option("toggle analogue");
          option("toggle vsync");
          option("return");
          menuyoff = -10;
          maxspacing = 15;
          break;
      case Menu::ed_settings:
          option("change description");
          option("edit scripts");
          option("change music");
          option("editor ghosts");
          option("load level");
          option("save level");
          option("options");
          option("quit to main menu");
  
          menuyoff = -20;
          maxspacing = 15;
          break;
      case Menu::ed_desc:
          option("change name");
          option("change author");
          option("change description");
          option("change website");
          option("back to settings");
  
          menuyoff = 6;
          maxspacing = 15;
          break;
      case Menu::ed_music:
          option("next song");
          option("previous song");
          option("back");
          menuyoff = 16;
          maxspacing = 15;
          break;
      case Menu::ed_quit:
          option("yes, save and quit");
          option("no, quit without saving");
          option("return to editor");
          menuyoff = 8;
          maxspacing = 15;
          break;
      case Menu::options:
          option("gameplay");
          option("graphics");
          option("audio");
          option("game pad");
          option("accessibility");
          option("return");
          menuyoff = 0;
          maxspacing = 15;
          break;
      case Menu::speedrunneroptions:
          option("glitchrunner mode");
          option("input delay");
          option("interact button");
          option("fake load screen");
          option("toggle in-game timer");
          option("return");
          menuyoff = 0;
          maxspacing = 15;
          break;
      case Menu::setglitchrunner:
      {
          int i;
  
          option("none");
  
          for (i = 1; i < GlitchrunnerNumVersions; ++i)
          {
              option(GlitchrunnerMode_enum_to_string((enum GlitchrunnerMode) i));
          }
          break;
      }
      case Menu::advancedoptions:
          option("unfocus pause");
          option("unfocus audio pause");
          option("room name background");
          option("return");
          menuyoff = 0;
          maxspacing = 15;
          break;
      case Menu::audiooptions:
          option("music volume");
          option("sound volume");
          if (music.mmmmmm)
          {
              option("soundtrack");
          }
          option("return");
          menuyoff = 0;
          maxspacing = 15;
          break;
      case Menu::accessibility:
  #if !defined(MAKEANDPLAY)
          option("unlock play modes");
  #endif
          option("invincibility", !ingame_titlemode || !incompetitive());
          option("slowdown", !ingame_titlemode || !incompetitive());
          option("animated backgrounds");
          option("screen effects");
          option("text outline");
          option("return");
          menuyoff = 0;
          maxspacing = 15;
          break;
      case Menu::controller:
          option("analog stick sensitivity");
          option("bind flip");
          option("bind enter");
          option("bind menu");
          option("bind restart");
          option("bind interact");
          option("return");
          menuyoff = 0;
          maxspacing = 10;
          break;
      case Menu::cleardatamenu:
      case Menu::clearcustomdatamenu:
          option("no! don't delete");
          option("yes, delete everything");
          menuyoff = 64;
          break;
      case Menu::setinvincibility:
          option("no, return to options");
          option("yes, enable");
          menuyoff = 64;
          break;
      case Menu::setslowdown:
          option("normal speed");
          option("80% speed");
          option("60% speed");
          option("40% speed");
          menuyoff = 16;
          break;
      case Menu::unlockmenu:
          option("unlock time trials");
          option("unlock intermissions", !unlock[16]);
          option("unlock no death mode", !unlock[17]);
          option("unlock flip mode", !unlock[18]);
          option("unlock ship jukebox", (stat_trinkets<20));
          option("unlock secret lab", !unlock[8]);
          option("return");
          menuyoff = -20;
          break;
      case Menu::credits:
          option("next page");
          option("last page");
          option("return");
          menuyoff = 64;
          break;
      case Menu::credits2:
          option("next page");
          option("previous page");
          option("return");
          menuyoff = 64;
          break;
      case Menu::credits25:
          option("next page");
          option("previous page");
          option("return");
          menuyoff = 64;
          break;
      case Menu::credits3:
          option("next page");
          option("previous page");
          option("return");
          menuyoff = 64;
          break;
      case Menu::credits4:
          option("next page");
          option("previous page");
          option("return");
          menuyoff = 64;
          break;
      case Menu::credits5:
          option("next page");
          option("previous page");
          option("return");
          menuyoff = 64;
          break;
      case Menu::credits6:
          option("first page");
          option("previous page");
          option("return");
          menuyoff = 64;
          break;
      case Menu::play:
      {
          //Ok, here's where the unlock stuff comes into it:
          //First up, time trials:
          int temp = 0;
          if (unlock[0] && stat_trinkets >= 3 && !unlocknotify[9]) temp++;
          if (unlock[1] && stat_trinkets >= 6 && !unlocknotify[10]) temp++;
          if (unlock[2] && stat_trinkets >= 9 && !unlocknotify[11]) temp++;
          if (unlock[3] && stat_trinkets >= 12 && !unlocknotify[12]) temp++;
          if (unlock[4] && stat_trinkets >= 15 && !unlocknotify[13]) temp++;
          if (unlock[5] && stat_trinkets >= 18 && !unlocknotify[14]) temp++;
          if (temp > 0)
          {
              //you've unlocked a time trial!
              if (unlock[0] && stat_trinkets >= 3)
              {
                  unlocknotify[9] = true;
                  unlock[9] = true;
              }
              if (unlock[1] && stat_trinkets >= 6)
              {
                  unlocknotify[10] = true;
                  unlock[10] = true;
              }
              if (unlock[2] && stat_trinkets >= 9)
              {
                  unlocknotify[11] = true;
                  unlock[11] = true;
              }
              if (unlock[3] && stat_trinkets >= 12)
              {
                  unlocknotify[12] = true;
                  unlock[12] = true;
              }
              if (unlock[4] && stat_trinkets >= 15)
              {
                  unlocknotify[13] = true;
                  unlock[13] = true;
              }
              if (unlock[5] && stat_trinkets >= 18)
              {
                  unlocknotify[14] = true;
                  unlock[14] = 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?
              temp = 0;
              if (bestrank[0] >= 2) temp++;
              if (bestrank[1] >= 2) temp++;
              if (bestrank[2] >= 2) temp++;
              if (bestrank[3] >= 2) temp++;
              if (bestrank[4] >= 2) temp++;
              if (bestrank[5] >= 2) temp++;
              if (temp >= 4 && !unlocknotify[17])
              {
                  //Unlock No Death Mode
                  unlocknotify[17] = true;
                  unlock[17] = true;
                  createmenu(Menu::unlocknodeathmode, true);
                  savestatsandsettings();
              }
              //Alright then! Flip mode?
              else if (unlock[5] && !unlocknotify[18])
              {
                  unlock[18] = true;
                  unlocknotify[18] = true;
                  createmenu(Menu::unlockflipmode, true);
                  savestatsandsettings();
              }
              //What about the intermission levels?
              else if (unlock[7] && !unlocknotify[16])
              {
                  unlock[16] = true;
                  unlocknotify[16] = true;
                  createmenu(Menu::unlockintermission, true);
                  savestatsandsettings();
              }
              else
              {
                  if (save_exists())
                  {
                      option("continue");
                  }
                  else
                  {
                      option("new game");
                  }
                  //ok, secret lab! no notification, but test:
                  if (unlock[8])
                  {
                      option("secret lab");
                  }
                  option("play modes");
                  if (save_exists())
                  {
                      option("new game");
                  }
                  option("return");
                  if (unlock[8])
                  {
                      menuyoff = -30;
                  }
                  else
                  {
                      menuyoff = -40;
                  }
              }
          }
          break;
      }
      case Menu::unlocktimetrial:
      case Menu::unlocktimetrials:
      case Menu::unlocknodeathmode:
      case Menu::unlockintermission:
      case Menu::unlockflipmode:
          option("continue");
          menuyoff = 70;
          break;
      case Menu::newgamewarning:
          option("start new game");
          option("return to menu");
          menuyoff = 64;
          break;
      case Menu::playmodes:
          option("time trials", !nocompetitive());
          option("intermissions", unlock[16]);
          option("no death mode", unlock[17] && !nocompetitive());
          option("flip mode", unlock[18]);
          option("return to play menu");
          menuyoff = 8;
          maxspacing = 20;
          break;
      case Menu::intermissionmenu:
          option("play intermission 1");
          option("play intermission 2");
          option("return to play menu");
          menuyoff = -35;
          break;
      case Menu::playint1:
          option("Vitellary");
          option("Vermilion");
          option("Verdigris");
          option("Victoria");
          option("return");
          menuyoff = 10;
          break;
      case Menu::playint2:
          option("Vitellary");
          option("Vermilion");
          option("Verdigris");
          option("Victoria");
          option("return");
          menuyoff = 10;
          break;
      case Menu::continuemenu:
          map.settowercolour(3);
          option("continue from teleporter");
          option("continue from quicksave");
          option("return to play menu");
          menuyoff = 20;
          break;
      case Menu::startnodeathmode:
          option("disable cutscenes");
          option("enable cutscenes");
          option("return to play menu");
          menuyoff = 40;
          break;
      case Menu::gameover:
          menucountdown = 120;
          menudest=Menu::gameover2;
          break;
      case Menu::gameover2:
          option("return to play menu");
          menuyoff = 80;
          break;
      case Menu::unlockmenutrials:
          option("space station 1", !unlock[9]);
          option("the laboratory", !unlock[10]);
          option("the tower", !unlock[11]);
          option("space station 2", !unlock[12]);
          option("the warp zone", !unlock[13]);
          option("the final level", !unlock[14]);
  
          option("return to unlock menu");
          menuyoff = 0;
          break;
      case Menu::timetrials:
          option(unlock[9] ? "space station 1" : "???", unlock[9]);
          option(unlock[10] ? "the laboratory" : "???", unlock[10]);
          option(unlock[11] ? "the tower" : "???", unlock[11]);
          option(unlock[12] ? "space station 2" : "???", unlock[12]);
          option(unlock[13] ? "the warp zone" : "???", unlock[13]);
          option(unlock[14] ? "the final level" : "???", unlock[14]);
  
          option("return to play menu");
          menuyoff = 0;
          maxspacing = 15;
          break;
      case Menu::nodeathmodecomplete:
          menucountdown = 90;
          menudest = Menu::nodeathmodecomplete2;
          break;
      case Menu::nodeathmodecomplete2:
          option("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("return to play menu");
          option("try again");
          menuyoff = 70;
          break;
      case Menu::gamecompletecontinue:
          option("return to play menu");
          menuyoff = 70;
          break;
      case Menu::errorsavingsettings:
          option("ok");
          option("silence");
          menuyoff = 10;
          break;
      case Menu::errorloadinglevel:
      case Menu::warninglevellist:
          option("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 + graphics.len(menuoptions[i].text);
              if (width > menuwidth)
                  menuwidth = width;
          }
      }
      menuxoff = (320-menuwidth)/2;
  }
  
  void Game::deletequick(void)
  {
      if( !FILESYSTEM_delete( "saves/qsave.vvv" ) )
          vlog_error("Error deleting saves/qsave.vvv");
      else
          quicksummary = "";
  }
  
  void Game::deletetele(void)
  {
      if( !FILESYSTEM_delete( "saves/tsave.vvv" ) )
          vlog_error("Error deleting saves/tsave.vvv");
      else
          telesummary = "";
  }
  
  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 telesummary != "" || quicksummary != "";
  }
  
  void Game::quittomenu(void)
  {
      gamestate = TITLEMODE;
      graphics.fademode = 4;
      FILESYSTEM_unmountAssets();
      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 (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
              returntomenu(Menu::playerworlds);
          }
      }
      else if (save_exists() || anything_unlocked())
      {
          returntomenu(Menu::play);
          if (!insecretlab)
          {
              //Select "continue"
              currentmenuoption = 0;
          }
      }
      else
      {
          createmenu(Menu::mainmenu);
      }
      script.hardreset();
  }
  
  void Game::returntolab(void)
  {
      gamestate = GAMEMODE;
      graphics.fademode = 4;
      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(11);
  }
  
  #if !defined(NO_CUSTOM_LEVELS) && !defined(NO_EDITOR)
  static void resetbg(void)
  {
      graphics.backgrounddrawn = false;
  }
  
  void Game::returntoeditor(void)
  {
      gamestate = EDITORMODE;
  
      graphics.textboxes.clear();
      hascontrol = true;
      advancetext = false;
      completestop = false;
      state = 0;
      graphics.showcutscenebars = false;
      graphics.fademode = 0;
  
      ed.keydelay = 6;
      ed.settingskey = true;
      ed.oldnotedelay = 0;
      ed.notedelay = 0;
      ed.roomnamehide = 0;
  
      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;
  }
  #endif
  
  static void returntoingametemp(void)
  {
      extern Game game;
      game.returntomenu(game.kludge_ingametemp);
  }
  
  #if !defined(NO_CUSTOM_LEVELS) && !defined(NO_EDITOR)
  static void returntoedsettings(void)
  {
      extern Game game;
      game.returntomenu(Menu::ed_settings);
  }
  #endif
  
  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 !defined(NO_CUSTOM_LEVELS) && !defined(NO_EDITOR)
      if (ingame_editormode)
      {
          ingame_editormode = false;
          DEFER_CALLBACK(returntoedsettings);
          gamestate = EDITORMODE;
          ed.settingskey = true;
      }
      else
  #endif
      {
          DEFER_CALLBACK(returntoingametemp);
          gamestate = MAPMODE;
          DEFER_CALLBACK(setflipmode);
          DEFER_CALLBACK(setfademode);
          if (!map.custommode && !graphics.setflipmode)
          {
              obj.flags[73] = true;
          }
      }
      DEFER_CALLBACK(nextbgcolor);
  }
  
  void Game::unlockAchievement(const char *name) {
  #if !defined(MAKEANDPLAY)
      if (!map.custommode) 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;
  
      if (prevgamestate == GAMEMODE)
      {
          graphics.menuoffset = 240;
      }
      else
      {
          graphics.menuoffset = 0;
      }
      graphics.oldmenuoffset = graphics.menuoffset;
  }
  
  void Game::copyndmresults(void)
  {
      ndmresultcrewrescued = crewrescued();
      ndmresulttrinkets = trinkets();
      ndmresulthardestroom = hardestroom;
      SDL_memcpy(ndmresultcrewstats, crewstats, sizeof(ndmresultcrewstats));
  }
  
  static inline int get_framerate(const int slowdown)
  {
      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)
  {
      switch (gamestate)
      {
      case EDITORMODE:
          return 24;
      case GAMEMODE:
          return get_framerate(slowdown);
      default:
          return 34;
      }
  }
  
  bool Game::incompetitive(void)
  {
      return (
          !map.custommode
          && swnmode
          && (swngame == 1 || swngame == 6 || swngame == 7)
      )
      || intimetrial
      || nodeathmode;
  }
  
  bool Game::nocompetitive(void)
  {
      return slowdown < 30 || map.invincibility;
  }
  
  bool Game::isingamecompletescreen()
  {
      return (state >= 3501 && state <= 3518) || (state >= 3520 && state <= 3522);
  }
  