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