/ source / blood / src / levels.cpp
levels.cpp
  1  //-------------------------------------------------------------------------
  2  /*
  3  Copyright (C) 2010-2019 EDuke32 developers and contributors
  4  Copyright (C) 2019 Nuke.YKT
  5  
  6  This file is part of NBlood.
  7  
  8  NBlood is free software; you can redistribute it and/or
  9  modify it under the terms of the GNU General Public License version 2
 10  as published by the Free Software Foundation.
 11  
 12  This program is distributed in the hope that it will be useful,
 13  but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 15  
 16  See the GNU General Public License for more details.
 17  
 18  You should have received a copy of the GNU General Public License
 19  along with this program; if not, write to the Free Software
 20  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 21  */
 22  //-------------------------------------------------------------------------
 23  #include <stdlib.h>
 24  #include <string.h>
 25  #include "compat.h"
 26  #include "common_game.h"
 27  
 28  #include "asound.h"
 29  #include "blood.h"
 30  #include "config.h"
 31  #include "credits.h"
 32  #include "endgame.h"
 33  #include "inifile.h"
 34  #include "levels.h"
 35  #include "loadsave.h"
 36  #include "messages.h"
 37  #include "network.h"
 38  #include "screen.h"
 39  #include "seq.h"
 40  #include "sound.h"
 41  #include "sfx.h"
 42  #include "view.h"
 43  #include "eventq.h"
 44  
 45  GAMEOPTIONS gGameOptions;
 46  
 47  GAMEOPTIONS gSingleGameOptions = {
 48      0,     // char nGameType;
 49      2,     // char nDifficulty;
 50      0,     // int nEpisode;
 51      0,     // int nLevel;
 52      "",    // char zLevelName[BMAX_PATH];
 53      "",    // char zLevelSong[BMAX_PATH];
 54      2,     // int nTrackNumber;
 55      "",    // char szSaveGameName[BMAX_PATH];
 56      "",    // char szUserGameName[BMAX_PATH];
 57      0,     // short nSaveGameSlot;
 58      0,     // int picEntry;
 59      0,     // unsigned int uMapCRC;
 60      1,     // char nMonsterSettings;
 61      kGameFlagNone, // int uGameFlags;
 62      kNetGameFlagNone, // int uNetGameFlags;
 63      0,     // char nWeaponSettings;
 64      0,     // char nItemSettings;
 65      0,     // char nRespawnSettings;
 66      2,     // char nTeamSettings;
 67      3600,  // int nMonsterRespawnTime;
 68      1800,  // int nWeaponRespawnTime;
 69      1800,  // int nItemRespawnTime;
 70      7200,  // int nSpecialRespawnTime;
 71      0,     // bool bQuadDamagePowerup;
 72      0,     // int nDamageInvul;
 73      0,     // int nProjectileBehavior;
 74      0,     // bool bNapalmFalloff;
 75      0,     // int nEnemyBehavior;
 76      0,     // bool bEnemyRandomTNT;
 77      0,     // short nWeaponsVer;
 78      0,     // short nAmmoScale;
 79      0,     // bool bSectorBehavior;
 80      0,     // char nHitscanProjectiles;
 81      0,     // char nGoreBehavior;
 82      0,     // char nRandomizerMode;
 83      "",    // char szRandomizerSeed[9];
 84      -1,    // int nRandomizerCheat;
 85      2,     // int nEnemyQuantity;
 86      2,     // int nEnemyHealth;
 87      0,     // int nEnemySpeed;
 88      0,     // int nPlayerSpeed;
 89      0,     // bool bEnemyShuffle;
 90      0,     // bool bPitchforkOnly;
 91      0,     // bool bPermaDeath;
 92      0,     // bool bFriendlyFire;
 93      1,     // char nKeySettings;
 94      0,     // char bItemWeaponSettings;
 95      1,     // char bAutoTeams;
 96      0,     // char nSpawnProtection;
 97      0,     // char nSpawnWeapon;
 98      BANNED_NONE, // unsigned int uSpriteBannedFlags;
 99      "",    // char szUserMap[BMAX_PATH];
100  };
101  
102  EPISODEINFO gEpisodeInfo[kMaxEpisodes+1];
103  
104  int gSkill = 2;
105  int gEpisodeCount;
106  int gNextLevel;
107  bool gGameStarted;
108  
109  int gLevelTime;
110  
111  char BloodIniFile[BMAX_PATH] = "BLOOD.INI";
112  char BloodIniPre[BMAX_PATH];
113  bool bINIOverride = false;
114  IniFile *BloodINI;
115  
116  
117  void levelInitINI(const char *pzIni)
118  {
119      int fp = kopen4loadfrommod(pzIni, 0);
120      if (fp < 0)
121          ThrowError("Initialization: %s does not exist", pzIni);
122      kclose(fp);
123      BloodINI = new IniFile(pzIni);
124      Bstrncpy(BloodIniFile, pzIni, BMAX_PATH);
125      Bstrncpy(BloodIniPre, pzIni, BMAX_PATH);
126      ChangeExtension(BloodIniPre, "");
127  }
128  
129  
130  void levelOverrideINI(const char *pzIni)
131  {
132      bINIOverride = true;
133      strcpy(BloodIniFile, pzIni);
134  }
135  
136  static char zSmkPath[BMAX_PATH], zWavPath[BMAX_PATH];
137  
138  void levelPlayIntroScene(int nEpisode, int nLevel)
139  {
140      sndStopSong();
141      sndKillAllSounds();
142      sfxKillAllSounds();
143      ambKillAll();
144      seqKillAll();
145      
146      GAMEOPTIONS* pOpt = &gGameOptions;
147      EPISODEINFO* pEpisode = &gEpisodeInfo[nEpisode];
148      LEVELINFO* pMap = &pEpisode->levelsInfo[nLevel];
149  
150      if (pOpt->uGameFlags & kGameFlagPlayIntro)
151      {
152          // this is episode enter cutscene
153          pOpt->uGameFlags &= ~kGameFlagPlayIntro;
154          if (!credPlaySmk(pEpisode->cutsceneASmkPath, pEpisode->cutsceneAWavPath, pEpisode->cutsceneAWavRsrcID)) // if failed (files not found), reattempt in movie directory
155          {
156              snprintf(zSmkPath, BMAX_PATH, "movie/%s", pEpisode->cutsceneASmkPath);
157              snprintf(zWavPath, BMAX_PATH, "movie/%s", pEpisode->cutsceneAWavPath);
158              credPlaySmk(zSmkPath, zWavPath, pEpisode->cutsceneAWavRsrcID);
159          }
160      }
161  
162      if (pOpt->uGameFlags & kGameFlagPlayScene)
163      {
164          // this is map enter cutscene
165          pOpt->uGameFlags &= ~kGameFlagPlayScene;
166          if (pMap->cutVideo[0])
167          {
168              sndKillAllSounds();
169              credPlaySmk(pMap->cutVideo, pMap->cutSound, 0);
170          }
171      }
172  
173      scrSetDac();
174      viewResizeView(gViewSize);
175      credReset();
176      scrSetDac();
177      ControlInfo info;
178      CONTROL_GetInput(&info); // clear mouse and all input after cutscene has finished playing
179      ctrlClearAllInput();
180  }
181  
182  void levelPlayEndScene(int nEpisode, int nLevel)
183  {
184      UNREFERENCED_PARAMETER(nLevel);
185  
186      gGameOptions.uGameFlags &= ~kGameFlagPlayOutro;
187      sndStopSong();
188      sndKillAllSounds();
189      sfxKillAllSounds();
190      ambKillAll();
191      seqKillAll();
192      EPISODEINFO *pEpisode = &gEpisodeInfo[nEpisode];
193      if (!credPlaySmk(pEpisode->cutsceneBSmkPath, pEpisode->cutsceneBWavPath, pEpisode->cutsceneBWavRsrcID)) // if failed (files not found), reattempt in movie directory
194      {
195          snprintf(zSmkPath, BMAX_PATH, "movie/%s", pEpisode->cutsceneBSmkPath);
196          snprintf(zWavPath, BMAX_PATH, "movie/%s", pEpisode->cutsceneBWavPath);
197          credPlaySmk(zSmkPath, zWavPath, pEpisode->cutsceneBWavRsrcID);
198      }
199      scrSetDac();
200      viewResizeView(gViewSize);
201      credReset();
202      scrSetDac();
203  }
204  
205  void levelClearSecrets(void)
206  {
207      gSecretMgr.Clear();
208  }
209  
210  void levelSetupSecret(int nCount)
211  {
212      gSecretMgr.SetCount(nCount);
213  }
214  
215  void levelTriggerSecret(int nSecret)
216  {
217      gSecretMgr.Found(nSecret);
218  }
219  
220  void CheckSectionAbend(const char *pzSection)
221  {
222      if (!pzSection || !BloodINI->SectionExists(pzSection))
223          ThrowError("Section [%s] expected in BLOOD.INI", pzSection);
224  }
225  
226  void CheckKeyAbend(const char *pzSection, const char *pzKey)
227  {
228      dassert(pzSection != NULL);
229  
230      if (!pzKey || !BloodINI->KeyExists(pzSection, pzKey))
231          ThrowError("Key %s expected in section [%s] of BLOOD.INI", pzKey, pzSection);
232  }
233  
234  LEVELINFO * levelGetInfoPtr(int nEpisode, int nLevel)
235  {
236      dassert(nEpisode >= 0 && nEpisode < gEpisodeCount);
237      EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[nEpisode];
238      dassert(nLevel >= 0 && nLevel < pEpisodeInfo->nLevels);
239      return &pEpisodeInfo->levelsInfo[nLevel];
240  }
241  
242  char * levelGetFilename(int nEpisode, int nLevel)
243  {
244      dassert(nEpisode >= 0 && nEpisode < gEpisodeCount);
245      EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[nEpisode];
246      dassert(nLevel >= 0 && nLevel < pEpisodeInfo->nLevels);
247      return pEpisodeInfo->levelsInfo[nLevel].Filename;
248  }
249  
250  char * levelGetMessage(int nMessage)
251  {
252      int nEpisode = gGameOptions.nEpisode;
253      int nLevel = gGameOptions.nLevel;
254      dassert(nMessage < kMaxMessages);
255      char *pMessage = gEpisodeInfo[nEpisode].levelsInfo[nLevel].Messages[nMessage];
256      if (*pMessage == 0)
257          return NULL;
258      if (VanillaMode() && (Bstrlen(pMessage) > 63)) // don't print incompatible messages for vanilla mode
259      {
260          OSD_Printf("> Warning: Level E%dM%d message #%d longer than 63 characters (incompatible with vanilla mode).\n", nEpisode, nLevel, nMessage);
261          pMessage[63] = '\0'; // terminate message to emulate vanilla 1.21 string size
262      }
263      return pMessage;
264  }
265  
266  char * levelGetTitle(void)
267  {
268      int nEpisode = gGameOptions.nEpisode;
269      int nLevel = gGameOptions.nLevel;
270      char *pTitle = gEpisodeInfo[nEpisode].levelsInfo[nLevel].Title;
271      if (*pTitle == 0)
272          return NULL;
273      for (int i = Bstrlen(pTitle)-1; i > 0; i--)
274      {
275          if ((pTitle[i] == '/') || (pTitle[i] == '\\'))
276              return &pTitle[i+1];
277      }
278      return pTitle;
279  }
280  
281  char * levelGetAuthor(void)
282  {
283      int nEpisode = gGameOptions.nEpisode;
284      int nLevel = gGameOptions.nLevel;
285      char *pAuthor = gEpisodeInfo[nEpisode].levelsInfo[nLevel].Author;
286      if (*pAuthor == 0)
287          return NULL;
288      return pAuthor;
289  }
290  
291  void levelSetupOptions(int nEpisode, int nLevel)
292  {
293      gGameOptions.nEpisode = nEpisode;
294      gGameOptions.nLevel = nLevel;
295      strcpy(gGameOptions.zLevelName, gEpisodeInfo[nEpisode].levelsInfo[nLevel].Filename);
296      gGameOptions.uMapCRC = dbReadMapCRC(gGameOptions.zLevelName);
297      // strcpy(gGameOptions.zLevelSong, gEpisodeInfo[nEpisode].at28[nLevel].atd0);
298      gGameOptions.nTrackNumber = gEpisodeInfo[nEpisode].levelsInfo[nLevel].SongId;
299  }
300  
301  void levelLoadMapInfo(IniFile *pIni, LEVELINFO *pLevelInfo, const char *pzSection)
302  {
303      char buffer[16];
304      strncpy(pLevelInfo->Title, pIni->GetKeyString(pzSection, "Title", pLevelInfo->Filename), 31);
305      strncpy(pLevelInfo->Author, pIni->GetKeyString(pzSection, "Author", ""), 31);
306      strncpy(pLevelInfo->Song, pIni->GetKeyString(pzSection, "Song", ""), BMAX_PATH);
307      pLevelInfo->SongId = pIni->GetKeyInt(pzSection, "Track", -1);
308      pLevelInfo->EndingA = pIni->GetKeyInt(pzSection, "EndingA", -1);
309      pLevelInfo->EndingB = pIni->GetKeyInt(pzSection, "EndingB", -1);
310      pLevelInfo->Fog = pIni->GetKeyInt(pzSection, "Fog", -0);
311      pLevelInfo->Weather = pIni->GetKeyInt(pzSection, "Weather", -0);
312  
313  #ifdef NOONE_EXTENSIONS
314      strncpy(pLevelInfo->cutVideo, pIni->GetKeyString(pzSection, "CutScene", ""), BMAX_PATH);
315      strncpy(pLevelInfo->cutSound, pIni->GetKeyString(pzSection, "CutWav",   ""), BMAX_PATH);
316      pLevelInfo->showEndScr = pIni->GetKeyBool(pzSection, "ShowEndingScreen", 1);
317      if (gGameOptions.nGameType != kGameTypeSinglePlayer)
318          pLevelInfo->showEndScr = 1;
319  
320  #endif
321  
322      for (int i = 0; i < kMaxMessages; i++)
323      {
324          sprintf(buffer, "Message%d", i+1);
325          strncpy(pLevelInfo->Messages[i], pIni->GetKeyString(pzSection, buffer, ""), 127);
326      }
327  }
328  
329  extern void MenuSetupEpisodeInfo(void);
330  
331  void levelLoadDefaults(void)
332  {
333      char buffer[64];
334      char buffer2[16];
335      levelInitINI(pINISelected->zName);
336      memset(gEpisodeInfo, 0, sizeof(gEpisodeInfo));
337      strncpy(gEpisodeInfo[MUS_INTRO/kMaxLevels].levelsInfo[MUS_INTRO%kMaxLevels].Song, "PESTIS", BMAX_PATH);
338      int i;
339  #ifdef NOONE_EXTENSIONS
340      const char* pTitle = BloodINI->GetKeyString("General", "Title");
341      if (pTitle)
342          strncpy(gGameTitle, pTitle, 31);
343  
344      gMenuColor  = ClipRange(BloodINI->GetKeyInt("General", "MainBackgroundColor", gMenuColor), 0, 255);
345      gMenuPicnum = ClipRange(BloodINI->GetKeyInt("General", "MainBackgroundTile", gMenuPicnum), 0, MAXUSERTILES);
346  
347      gStatsPicnum = BloodINI->GetKeyInt("General", "StatsBackgroundTile", gStatsPicnum);
348      i = BloodINI->GetKeyInt("General", "StatsBackgroundColor", gStatsColor);
349      gStatsColor  = rngok(i, 0, 255) ? i : 255;
350  #endif
351  
352      for (i = 0; i < kMaxEpisodes; i++)
353      {
354          sprintf(buffer, "Episode%d", i+1);
355          if (!BloodINI->SectionExists(buffer))
356              break;
357          EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[i];
358          strncpy(pEpisodeInfo->title, BloodINI->GetKeyString(buffer, "Title", buffer), 31);
359          strncpy(pEpisodeInfo->cutsceneASmkPath, BloodINI->GetKeyString(buffer, "CutSceneA", ""), BMAX_PATH);
360          pEpisodeInfo->cutsceneAWavRsrcID = BloodINI->GetKeyInt(buffer, "CutWavA", -1);
361          if (pEpisodeInfo->cutsceneAWavRsrcID == 0)
362              strncpy(pEpisodeInfo->cutsceneAWavPath, BloodINI->GetKeyString(buffer, "CutWavA", ""), BMAX_PATH);
363          else
364              pEpisodeInfo->cutsceneAWavPath[0] = 0;
365          strncpy(pEpisodeInfo->cutsceneBSmkPath, BloodINI->GetKeyString(buffer, "CutSceneB", ""), BMAX_PATH);
366          pEpisodeInfo->cutsceneBWavRsrcID = BloodINI->GetKeyInt(buffer, "CutWavB", -1);
367          if (pEpisodeInfo->cutsceneBWavRsrcID == 0)
368              strncpy(pEpisodeInfo->cutsceneBWavPath, BloodINI->GetKeyString(buffer, "CutWavB", ""), BMAX_PATH);
369          else
370              pEpisodeInfo->cutsceneBWavPath[0] = 0;
371  
372          pEpisodeInfo->bloodbath = BloodINI->GetKeyInt(buffer, "BloodBathOnly", 0);
373          pEpisodeInfo->cutALevel = BloodINI->GetKeyInt(buffer, "CutSceneALevel", 0);
374          if (pEpisodeInfo->cutALevel > 0)
375              pEpisodeInfo->cutALevel--;
376          int j;
377          for (j = 0; j < kMaxLevels; j++)
378          {
379              LEVELINFO *pLevelInfo = &pEpisodeInfo->levelsInfo[j];
380              sprintf(buffer2, "Map%d", j+1);
381              if (!BloodINI->KeyExists(buffer, buffer2))
382                  break;
383              const char *pMap = BloodINI->GetKeyString(buffer, buffer2, NULL);
384              CheckSectionAbend(pMap);
385              strncpy(pLevelInfo->Filename, pMap, BMAX_PATH);
386              levelLoadMapInfo(BloodINI, pLevelInfo, pMap);
387          }
388          pEpisodeInfo->nLevels = j;
389      }
390      gEpisodeCount = i;
391      MenuSetupEpisodeInfo();
392  }
393  
394  void levelAddUserMap(const char *pzMap)
395  {
396      char buffer[BMAX_PATH];
397      //strcpy(buffer, g_modDir);
398      strncpy(buffer, pzMap, BMAX_PATH);
399      ChangeExtension(buffer, ".DEF");
400  
401      IniFile UserINI(buffer);
402      int nEpisode = ClipRange(UserINI.GetKeyInt(NULL, "Episode", 0), 0, 5);
403      EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[nEpisode];
404      int nLevel = ClipRange(UserINI.GetKeyInt(NULL, "Level", pEpisodeInfo->nLevels), 0, 15);
405      if (nLevel >= pEpisodeInfo->nLevels)
406      {
407          if (pEpisodeInfo->nLevels == 0)
408          {
409              gEpisodeCount++;
410              sprintf(pEpisodeInfo->title, "Episode %d", nEpisode);
411          }
412          nLevel = pEpisodeInfo->nLevels++;
413      }
414      LEVELINFO *pLevelInfo = &pEpisodeInfo->levelsInfo[nLevel];
415      ChangeExtension(buffer, "");
416      strncpy(pLevelInfo->Filename, buffer, BMAX_PATH);
417      levelLoadMapInfo(&UserINI, pLevelInfo, NULL);
418      gGameOptions.nEpisode = nEpisode;
419      gGameOptions.nLevel = nLevel;
420      gGameOptions.uMapCRC = dbReadMapCRC(pLevelInfo->Filename);
421      strcpy(gGameOptions.zLevelName, pLevelInfo->Filename);
422      MenuSetupEpisodeInfo();
423  }
424  
425  void levelGetNextLevels(int nEpisode, int nLevel, int *pnEndingA, int *pnEndingB)
426  {
427      dassert(pnEndingA != NULL && pnEndingB != NULL);
428      LEVELINFO *pLevelInfo = &gEpisodeInfo[nEpisode].levelsInfo[nLevel];
429      int nEndingA = pLevelInfo->EndingA;
430      if (nEndingA >= 0)
431          nEndingA--;
432      int nEndingB = pLevelInfo->EndingB;
433      if (nEndingB >= 0)
434          nEndingB--;
435      *pnEndingA = nEndingA;
436      *pnEndingB = nEndingB;
437  }
438  
439  void levelEndLevel(int nExitType)
440  {
441      int nEndingA, nEndingB;
442      EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[gGameOptions.nEpisode];
443      gGameOptions.uGameFlags |= kGameFlagContinuing;
444      levelGetNextLevels(gGameOptions.nEpisode, gGameOptions.nLevel, &nEndingA, &nEndingB);
445      switch (nExitType)
446      {
447      case kLevelExitNormal:
448          if (nEndingA == -1) // no more levels (reached end of episode), trigger credits
449          {
450              if (pEpisodeInfo->cutsceneBSmkPath[0]) // if ending cutscene file present, set to play movie
451                  gGameOptions.uGameFlags |= kGameFlagPlayOutro;
452              gGameOptions.nLevel = 0;
453              gGameOptions.uGameFlags |= kGameFlagEnding;
454          }
455          else
456              gNextLevel = nEndingA;
457          break;
458      case kLevelExitSecret:
459          if (nEndingB == -1) // no more levels (reached end of episode), trigger credits
460          {
461              if (gGameOptions.nEpisode + 1 < gEpisodeCount)
462              {
463                  if (pEpisodeInfo->cutsceneBSmkPath[0]) // if ending cutscene file present, set to play movie
464                      gGameOptions.uGameFlags |= kGameFlagPlayOutro;
465                  gGameOptions.nLevel = 0;
466                  gGameOptions.uGameFlags |= kGameFlagEnding;
467              }
468              else
469              {
470                  gGameOptions.nLevel = 0;
471                  gGameOptions.uGameFlags |= kGameFlagContinuing;
472              }
473          }
474          else
475              gNextLevel = nEndingB;
476          break;
477      }
478  }
479  
480  void levelRestart(void)
481  {
482      levelSetupOptions(gGameOptions.nEpisode, gGameOptions.nLevel);
483      gStartNewGame = true;
484      gAutosaveInCurLevel = false;
485  }
486  
487  int levelGetMusicIdx(const char *str)
488  {
489      int32_t lev, ep;
490      signed char b1, b2;
491  
492      int numMatches = sscanf(str, "%c%d%c%d", &b1, &ep, &b2, &lev);
493  
494      if (numMatches != 4 || Btoupper(b1) != 'E' || Btoupper(b2) != 'L')
495          return -1;
496  
497      if ((unsigned)--lev >= kMaxLevels || (unsigned)--ep >= kMaxEpisodes)
498          return -2;
499  
500      return (ep * kMaxLevels) + lev;
501  }
502  
503  bool levelTryPlayMusic(int nEpisode, int nLevel, bool bSetLevelSong)
504  {
505      char buffer[BMAX_PATH];
506      if (CDAudioToggle && gEpisodeInfo[nEpisode].levelsInfo[nLevel].SongId > 0 && (!CDAudioFallback || gEpisodeInfo[nEpisode].levelsInfo[nLevel].Song[0] == '\0'))
507          snprintf(buffer, BMAX_PATH, "blood%02i.ogg", gEpisodeInfo[nEpisode].levelsInfo[nLevel].SongId);
508      else
509          strncpy(buffer, gEpisodeInfo[nEpisode].levelsInfo[nLevel].Song, BMAX_PATH);
510      bool bReturn = !!sndPlaySong(buffer, true);
511      if (!bReturn || bSetLevelSong)
512          strncpy(gGameOptions.zLevelSong, buffer, BMAX_PATH);
513      return bReturn;
514  }
515  
516  void levelTryPlayMusicOrNothing(int nEpisode, int nLevel)
517  {
518      if (levelTryPlayMusic(nEpisode, nLevel, true))
519          sndStopSong();
520  }
521  
522  class LevelsLoadSave : public LoadSave
523  {
524      virtual void Load(void);
525      virtual void Save(void);
526  };
527  
528  
529  static LevelsLoadSave *myLoadSave;
530  
531  void LevelsLoadSave::Load(void)
532  {
533      Read(&gNextLevel, sizeof(gNextLevel));
534      Read(&gGameOptions, sizeof(gGameOptions));
535      Read(&gGameStarted, sizeof(gGameStarted));
536  }
537  
538  void LevelsLoadSave::Save(void)
539  {
540      Write(&gNextLevel, sizeof(gNextLevel));
541      Write(&gGameOptions, sizeof(gGameOptions));
542      Write(&gGameStarted, sizeof(gGameStarted));
543  }
544  
545  void LevelsLoadSaveConstruct(void)
546  {
547      myLoadSave = new LevelsLoadSave();
548  }
549