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 "build.h"
24 #include "mmulti.h"
25 #include "compat.h"
26 #include "renderlayer.h"
27 #include "vfs.h"
28 #include "fx_man.h"
29 #include "common.h"
30 #include "common_game.h"
31 #include "gamedefs.h"
32
33 #include "asound.h"
34 #include "db.h"
35 #include "blood.h"
36 #include "choke.h"
37 #include "config.h"
38 #include "controls.h"
39 #include "credits.h"
40 #include "demo.h"
41 #include "dude.h"
42 #include "endgame.h"
43 #include "eventq.h"
44 #include "fire.h"
45 #include "fx.h"
46 #include "gib.h"
47 #include "getopt.h"
48 #include "globals.h"
49 #include "gui.h"
50 #include "levels.h"
51 #include "loadsave.h"
52 #include "menu.h"
53 #include "mirrors.h"
54 #include "music.h"
55 #include "network.h"
56 #include "osdcmds.h"
57 #include "replace.h"
58 #include "resource.h"
59 #include "qheap.h"
60 #include "screen.h"
61 #include "sectorfx.h"
62 #include "seq.h"
63 #include "sfx.h"
64 #include "sound.h"
65 #include "tile.h"
66 #include "trig.h"
67 #include "triggers.h"
68 #include "view.h"
69 #include "warp.h"
70 #include "weapon.h"
71 #ifdef NOONE_EXTENSIONS
72 #include "nnexts.h"
73 #endif
74
75 #ifdef _WIN32
76 # include <shellapi.h>
77 # define UPDATEINTERVAL 604800 // 1w
78 # include "winbits.h"
79 #else
80 # ifndef GEKKO
81 # include <sys/ioctl.h>
82 # endif
83 #endif /* _WIN32 */
84
85 const char* AppProperName = APPNAME;
86 const char* AppTechnicalName = APPBASENAME;
87
88 char SetupFilename[BMAX_PATH] = SETUPFILENAME;
89 int32_t gNoSetup = 0, gCommandSetup = 0;
90
91 INPUT_MODE gInputMode;
92
93 #ifdef USE_QHEAP
94 unsigned int nMaxAlloc = 0x4000000;
95 #endif
96
97 bool bCustomName = false;
98 char bAddUserMap = false;
99 bool bNoDemo = false;
100 bool bQuickStart = false;
101 bool bNoAutoLoad = false;
102
103 int gMusicPrevLoadedEpisode = -1;
104 int gMusicPrevLoadedLevel = -1;
105
106 char gUserMapFilename[BMAX_PATH];
107 char gPName[MAXPLAYERNAME];
108
109 short BloodVersion = 0x115;
110
111 int gNetPlayers;
112
113 char *pUserTiles = NULL;
114 char *pUserSoundRFF = NULL;
115 char *pUserRFF = NULL;
116
117 int gChokeCounter = 0;
118
119 double g_gameUpdateTime, g_gameUpdateAndDrawTime;
120 double g_gameUpdateAvgTime = 0.001;
121
122 int gSaveGameNum;
123 bool gQuitGame;
124 int gQuitRequest;
125 bool gPaused;
126 bool gSaveGameActive;
127 int gCacheMiss;
128
129 enum gametokens
130 {
131 T_INCLUDE = 0,
132 T_INTERFACE = 0,
133 T_LOADGRP = 1,
134 T_MODE = 1,
135 T_CACHESIZE = 2,
136 T_ALLOW = 2,
137 T_NOAUTOLOAD,
138 T_INCLUDEDEFAULT,
139 T_MUSIC,
140 T_SOUND,
141 T_FILE,
142 //T_CUTSCENE,
143 //T_ANIMSOUNDS,
144 //T_NOFLOORPALRANGE,
145 T_ID,
146 T_MINPITCH,
147 T_MAXPITCH,
148 T_PRIORITY,
149 T_TYPE,
150 T_DISTANCE,
151 T_VOLUME,
152 T_DELAY,
153 T_RENAMEFILE,
154 T_GLOBALGAMEFLAGS,
155 T_ASPECT,
156 T_FORCEFILTER,
157 T_FORCENOFILTER,
158 T_TEXTUREFILTER,
159 T_RFFDEFINEID,
160 T_TILEFROMTEXTURE,
161 T_IFCRC, T_IFMATCH, T_CRC32,
162 T_SIZE,
163 T_SURFACE,
164 T_VOXEL,
165 T_VIEW,
166 T_SHADE,
167 };
168
169 int blood_globalflags;
170
app_crashhandler(void)171 void app_crashhandler(void)
172 {
173 // NUKE-TODO:
174 }
175
M32RunScript(const char * s)176 void M32RunScript(const char *s)
177 {
178 UNREFERENCED_PARAMETER(s);
179 }
180
ShutDown(void)181 void ShutDown(void)
182 {
183 if (!in3dmode())
184 return;
185 CONFIG_WriteSetup(0);
186 netDeinitialize();
187 sndTerm();
188 sfxTerm();
189 scrUnInit();
190 CONTROL_Shutdown();
191 KB_Shutdown();
192 OSD_Cleanup();
193 // PORT_TODO: Check argument
194 if (syncstate)
195 printf("A packet was lost! (syncstate)\n");
196 for (int i = 0; i < 10; i++)
197 {
198 if (gSaveGamePic[i])
199 Resource::Free(gSaveGamePic[i]);
200 }
201 DO_FREE_AND_NULL(pUserTiles);
202 DO_FREE_AND_NULL(pUserSoundRFF);
203 DO_FREE_AND_NULL(pUserRFF);
204 }
205
QuitGame(void)206 void QuitGame(void)
207 {
208 ShutDown();
209 exit(0);
210 }
211
PrecacheDude(spritetype * pSprite)212 void PrecacheDude(spritetype *pSprite)
213 {
214 DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
215 seqPrecacheId(pDudeInfo->seqStartID);
216 seqPrecacheId(pDudeInfo->seqStartID+5);
217 seqPrecacheId(pDudeInfo->seqStartID+1);
218 seqPrecacheId(pDudeInfo->seqStartID+2);
219 switch (pSprite->type)
220 {
221 case kDudeCultistTommy:
222 case kDudeCultistShotgun:
223 case kDudeCultistTesla:
224 case kDudeCultistTNT:
225 seqPrecacheId(pDudeInfo->seqStartID+6);
226 seqPrecacheId(pDudeInfo->seqStartID+7);
227 seqPrecacheId(pDudeInfo->seqStartID+8);
228 seqPrecacheId(pDudeInfo->seqStartID+9);
229 seqPrecacheId(pDudeInfo->seqStartID+13);
230 seqPrecacheId(pDudeInfo->seqStartID+14);
231 seqPrecacheId(pDudeInfo->seqStartID+15);
232 break;
233 case kDudeZombieButcher:
234 case kDudeGillBeast:
235 seqPrecacheId(pDudeInfo->seqStartID+6);
236 seqPrecacheId(pDudeInfo->seqStartID+7);
237 seqPrecacheId(pDudeInfo->seqStartID+8);
238 seqPrecacheId(pDudeInfo->seqStartID+9);
239 seqPrecacheId(pDudeInfo->seqStartID+10);
240 seqPrecacheId(pDudeInfo->seqStartID+11);
241 break;
242 case kDudeGargoyleStatueFlesh:
243 case kDudeGargoyleStatueStone:
244 seqPrecacheId(pDudeInfo->seqStartID+6);
245 seqPrecacheId(pDudeInfo->seqStartID+6);
246 fallthrough__;
247 case kDudeGargoyleFlesh:
248 case kDudeGargoyleStone:
249 seqPrecacheId(pDudeInfo->seqStartID+6);
250 seqPrecacheId(pDudeInfo->seqStartID+7);
251 seqPrecacheId(pDudeInfo->seqStartID+8);
252 seqPrecacheId(pDudeInfo->seqStartID+9);
253 break;
254 case kDudePhantasm:
255 case kDudeHellHound:
256 case kDudeSpiderBrown:
257 case kDudeSpiderRed:
258 case kDudeSpiderBlack:
259 case kDudeSpiderMother:
260 case kDudeTchernobog:
261 seqPrecacheId(pDudeInfo->seqStartID+6);
262 seqPrecacheId(pDudeInfo->seqStartID+7);
263 seqPrecacheId(pDudeInfo->seqStartID+8);
264 break;
265 case kDudeCerberusTwoHead:
266 seqPrecacheId(pDudeInfo->seqStartID+6);
267 seqPrecacheId(pDudeInfo->seqStartID+7);
268 fallthrough__;
269 case kDudeHand:
270 case kDudeBoneEel:
271 case kDudeBat:
272 case kDudeRat:
273 seqPrecacheId(pDudeInfo->seqStartID+6);
274 seqPrecacheId(pDudeInfo->seqStartID+7);
275 break;
276 case kDudeCultistBeast:
277 seqPrecacheId(pDudeInfo->seqStartID+6);
278 break;
279 case kDudeZombieAxeBuried:
280 seqPrecacheId(pDudeInfo->seqStartID+12);
281 seqPrecacheId(pDudeInfo->seqStartID+9);
282 fallthrough__;
283 case kDudeZombieAxeLaying:
284 seqPrecacheId(pDudeInfo->seqStartID+10);
285 fallthrough__;
286 case kDudeZombieAxeNormal:
287 seqPrecacheId(pDudeInfo->seqStartID+6);
288 seqPrecacheId(pDudeInfo->seqStartID+7);
289 seqPrecacheId(pDudeInfo->seqStartID+8);
290 seqPrecacheId(pDudeInfo->seqStartID+11);
291 seqPrecacheId(pDudeInfo->seqStartID+13);
292 seqPrecacheId(pDudeInfo->seqStartID+14);
293 break;
294 }
295 }
296
PrecacheThing(spritetype * pSprite)297 void PrecacheThing(spritetype *pSprite) {
298 switch (pSprite->type) {
299 case kThingGlassWindow: // worthless...
300 case kThingFluorescent:
301 seqPrecacheId(12);
302 break;
303 case kThingSpiderWeb:
304 seqPrecacheId(15);
305 break;
306 case kThingMetalGrate:
307 seqPrecacheId(21);
308 break;
309 case kThingFlammableTree:
310 seqPrecacheId(25);
311 seqPrecacheId(26);
312 break;
313 case kTrapMachinegun:
314 seqPrecacheId(38);
315 seqPrecacheId(40);
316 seqPrecacheId(28);
317 break;
318 case kThingObjectGib:
319 //case kThingObjectExplode: weird that only gib object is precached and this one is not
320 break;
321 }
322 tilePrecacheTile(pSprite->picnum);
323 }
324
PreloadTiles(void)325 void PreloadTiles(void)
326 {
327 nPrecacheCount = 0;
328 int skyTile = -1;
329 memset(gotpic,0,sizeof(gotpic));
330 // Fonts
331 for (int i = 0; i < kFontNum; i++)
332 {
333 for (int j = 0; j < 96; j++)
334 {
335 tilePrecacheTile(gFont[i].tile + j, 0);
336 }
337 }
338 for (int i = 0; i < numsectors; i++)
339 {
340 tilePrecacheTile(sector[i].floorpicnum, 0);
341 tilePrecacheTile(sector[i].ceilingpicnum, 0);
342 if ((sector[i].ceilingstat&1) != 0 && skyTile == -1)
343 skyTile = sector[i].ceilingpicnum;
344 }
345 for (int i = 0; i < numwalls; i++)
346 {
347 tilePrecacheTile(wall[i].picnum, 0);
348 if (wall[i].overpicnum >= 0)
349 tilePrecacheTile(wall[i].overpicnum, 0);
350 }
351 for (int i = 0; i < kMaxSprites; i++)
352 {
353 if (sprite[i].statnum < kMaxStatus)
354 {
355 spritetype *pSprite = &sprite[i];
356 switch (pSprite->statnum)
357 {
358 case kStatDude:
359 PrecacheDude(pSprite);
360 break;
361 case kStatThing:
362 PrecacheThing(pSprite);
363 break;
364 default:
365 tilePrecacheTile(pSprite->picnum);
366 break;
367 }
368 }
369 }
370
371 // Precache common SEQs
372 for (int i = 0; i < 100; i++)
373 {
374 seqPrecacheId(i);
375 }
376
377 tilePrecacheTile(1147); // water drip
378 tilePrecacheTile(1160); // blood drip
379
380 // Player SEQs
381 seqPrecacheId(dudeInfo[31].seqStartID+6);
382 seqPrecacheId(dudeInfo[31].seqStartID+7);
383 seqPrecacheId(dudeInfo[31].seqStartID+8);
384 seqPrecacheId(dudeInfo[31].seqStartID+9);
385 seqPrecacheId(dudeInfo[31].seqStartID+10);
386 seqPrecacheId(dudeInfo[31].seqStartID+14);
387 seqPrecacheId(dudeInfo[31].seqStartID+15);
388 seqPrecacheId(dudeInfo[31].seqStartID+12);
389 seqPrecacheId(dudeInfo[31].seqStartID+16);
390 seqPrecacheId(dudeInfo[31].seqStartID+17);
391 seqPrecacheId(dudeInfo[31].seqStartID+18);
392
393 if (skyTile > -1 && skyTile < kMaxTiles)
394 {
395 for (int i = 1; i < gSkyCount; i++)
396 tilePrecacheTile(skyTile+i, 0);
397 }
398
399 WeaponPrecache();
400 viewPrecacheTiles();
401 fxPrecache();
402 gibPrecache();
403
404 gameHandleEvents();
405 }
406
407 #ifdef USE_OPENGL
PrecacheExtraTextureMaps(int nTile)408 void PrecacheExtraTextureMaps(int nTile)
409 {
410 // PRECACHE
411 if (useprecache && bpp > 8)
412 {
413 for (int type = 0; type < 2 && !KB_KeyPressed(sc_Space); type++)
414 {
415 if (TestBitString(precachehightile[type], nTile))
416 {
417 for (int k = 0; k < MAXPALOOKUPS - RESERVEDPALS && !KB_KeyPressed(sc_Space); k++)
418 {
419 // this is the CROSSHAIR_PAL, see screens.cpp
420 if (k == MAXPALOOKUPS - RESERVEDPALS - 1)
421 break;
422 #ifdef POLYMER
423 if (videoGetRenderMode() != REND_POLYMER || !polymer_havehighpalookup(0, k))
424 #endif
425 polymost_precache(nTile, k, type);
426 }
427
428 #ifdef USE_GLEXT
429 if (r_detailmapping)
430 polymost_precache(nTile, DETAILPAL, type);
431
432 if (r_glowmapping)
433 polymost_precache(nTile, GLOWPAL, type);
434 #endif
435 #ifdef POLYMER
436 if (videoGetRenderMode() == REND_POLYMER)
437 {
438 if (pr_specularmapping)
439 polymost_precache(nTile, SPECULARPAL, type);
440
441 if (pr_normalmapping)
442 polymost_precache(nTile, NORMALPAL, type);
443 }
444 #endif
445 }
446 }
447 }
448 }
449 #endif
450
PreloadCache(void)451 void PreloadCache(void)
452 {
453 char tempbuf[128];
454 if (gDemo.at1)
455 return;
456 gSysRes.PurgeCache();
457 gSoundRes.PurgeCache();
458 gSysRes.PrecacheSounds();
459 gSoundRes.PrecacheSounds();
460 if (MusicRestartsOnLoadToggle)
461 sndTryPlaySpecialMusic(MUS_LOADING);
462 PreloadTiles();
463 ClockTicks clock = totalclock;
464 int cnt = 0;
465 int percentDisplayed = -1;
466
467 for (int i=0; i<kMaxTiles && !KB_KeyPressed(sc_Space); i++)
468 {
469 if (TestBitString(gotpic, i))
470 {
471 if (waloff[i] == 0)
472 tileLoad((int16_t)i);
473
474 #ifdef USE_OPENGL
475 PrecacheExtraTextureMaps(i);
476 #endif
477
478 MUSIC_Update();
479
480 if ((++cnt & 7) == 0)
481 gameHandleEvents();
482
483 if (videoGetRenderMode() != REND_CLASSIC && totalclock - clock > (kTicRate>>2))
484 {
485 int const percentComplete = min(100, tabledivide32_noinline(100 * cnt, nPrecacheCount));
486
487 // this just prevents the loading screen percentage bar from making large jumps
488 while (percentDisplayed < percentComplete)
489 {
490 gameHandleEvents();
491 Bsprintf(tempbuf, "Loaded %d%% (%d/%d textures)\n", percentDisplayed, cnt, nPrecacheCount);
492 viewLoadingScreenUpdate(tempbuf, percentDisplayed);
493 videoNextPage();
494
495 if (totalclock - clock >= 1)
496 {
497 clock = totalclock;
498 percentDisplayed++;
499 }
500 }
501
502 clock = totalclock;
503 }
504 }
505 }
506 memset(gotpic,0,sizeof(gotpic));
507 }
508
EndLevel(void)509 void EndLevel(void)
510 {
511 gViewPos = VIEWPOS_0;
512 gGameMessageMgr.Clear();
513 sndKillAllSounds();
514 sfxKillAllSounds();
515 ambKillAll();
516 seqKillAll();
517 }
518
G_TryMapHack(const char * mhkfile)519 int G_TryMapHack(const char* mhkfile)
520 {
521 int const failure = engineLoadMHK(mhkfile);
522
523 if (!failure)
524 initprintf("Loaded map hack file \"%s\"\n", mhkfile);
525
526 return failure;
527 }
528
G_LoadMapHack(char * outbuf,const char * filename)529 void G_LoadMapHack(char* outbuf, const char* filename)
530 {
531 if (filename != NULL)
532 Bstrcpy(outbuf, filename);
533
534 append_ext_UNSAFE(outbuf, ".mhk");
535
536 if (G_TryMapHack(outbuf) && usermaphacks != NULL)
537 {
538 auto pMapInfo = (usermaphack_t*)bsearch(&g_loadedMapHack, usermaphacks, num_usermaphacks,
539 sizeof(usermaphack_t), compare_usermaphacks);
540 if (pMapInfo)
541 G_TryMapHack(pMapInfo->mhkfile);
542 }
543 }
544
545 #ifdef POLYMER
G_RefreshLights(void)546 void G_RefreshLights(void)
547 {
548 if (Numsprites && videoGetRenderMode() == REND_POLYMER)
549 {
550 int statNum = 0;
551
552 do
553 {
554 int spriteNum = headspritestat[statNum++];
555
556 while (spriteNum >= 0)
557 {
558 actDoLight(spriteNum);
559 spriteNum = nextspritestat[spriteNum];
560 }
561 }
562 while (statNum < MAXSTATUS);
563 }
564 }
565
G_Polymer_UnInit(void)566 void G_Polymer_UnInit(void)
567 {
568 int32_t i;
569
570 for (i = 0; i < kMaxSprites; i++)
571 DeleteLight(i);
572 }
573 #endif // POLYMER
574
575
576 PLAYER gPlayerTemp[kMaxPlayers];
577 int gHealthTemp[kMaxPlayers];
578
579 vec3_t startpos;
580 int16_t startang, startsectnum;
581
StartLevel(GAMEOPTIONS * gameOptions)582 void StartLevel(GAMEOPTIONS *gameOptions)
583 {
584 EndLevel();
585 gInput = {};
586 gStartNewGame = 0;
587 ready2send = 0;
588 gMusicPrevLoadedEpisode = gGameOptions.nEpisode;
589 gMusicPrevLoadedLevel = gGameOptions.nLevel;
590 if (gDemo.at0 && gGameStarted)
591 gDemo.Close();
592 netWaitForEveryone(0);
593 if (gGameOptions.nGameType == 0)
594 {
595 if (!(gGameOptions.uGameFlags&1))
596 levelSetupOptions(gGameOptions.nEpisode, gGameOptions.nLevel);
597 if (gEpisodeInfo[gGameOptions.nEpisode].cutALevel == gGameOptions.nLevel
598 && gEpisodeInfo[gGameOptions.nEpisode].cutsceneASmkPath)
599 gGameOptions.uGameFlags |= 4;
600 if ((gGameOptions.uGameFlags&4) && gDemo.at1 == 0 && !Bstrlen(gGameOptions.szUserMap))
601 levelPlayIntroScene(gGameOptions.nEpisode);
602
603 ///////
604 gGameOptions.weaponsV10x = gWeaponsV10x;
605 ///////
606 }
607 else if (gGameOptions.nGameType > 0 && !(gGameOptions.uGameFlags&1))
608 {
609 gGameOptions.nEpisode = gPacketStartGame.episodeId;
610 gGameOptions.nLevel = gPacketStartGame.levelId;
611 gGameOptions.nGameType = gPacketStartGame.gameType;
612 gGameOptions.nDifficulty = gPacketStartGame.difficulty;
613 gGameOptions.nMonsterSettings = gPacketStartGame.monsterSettings;
614 gGameOptions.nWeaponSettings = gPacketStartGame.weaponSettings;
615 gGameOptions.nItemSettings = gPacketStartGame.itemSettings;
616 gGameOptions.nRespawnSettings = gPacketStartGame.respawnSettings;
617 gGameOptions.bFriendlyFire = gPacketStartGame.bFriendlyFire;
618 gGameOptions.bKeepKeysOnRespawn = gPacketStartGame.bKeepKeysOnRespawn;
619 if (gPacketStartGame.userMap)
620 levelAddUserMap(gPacketStartGame.userMapName);
621 else
622 levelSetupOptions(gGameOptions.nEpisode, gGameOptions.nLevel);
623
624 ///////
625 gGameOptions.weaponsV10x = gPacketStartGame.weaponsV10x;
626 ///////
627
628 gBlueFlagDropped = false;
629 gRedFlagDropped = false;
630 }
631 if (gameOptions->uGameFlags&1)
632 {
633 for (int i = connecthead; i >= 0; i = connectpoint2[i])
634 {
635 memcpy(&gPlayerTemp[i],&gPlayer[i],sizeof(PLAYER));
636 gHealthTemp[i] = xsprite[gPlayer[i].pSprite->extra].health;
637 }
638 }
639 bVanilla = gDemo.at1 && gDemo.m_bLegacy;
640 enginecompatibilitymode = ENGINE_19960925;//bVanilla;
641 memset(xsprite,0,sizeof(xsprite));
642 memset(sprite,0,kMaxSprites*sizeof(spritetype));
643 drawLoadingScreen();
644 if (dbLoadMap(gameOptions->zLevelName,(int*)&startpos.x,(int*)&startpos.y,(int*)&startpos.z,&startang,&startsectnum,(unsigned int*)&gameOptions->uMapCRC))
645 {
646 gQuitGame = true;
647 return;
648 }
649 char levelName[BMAX_PATH];
650 G_LoadMapHack(levelName, gameOptions->zLevelName);
651 wsrand(gameOptions->uMapCRC);
652 gKillMgr.Clear();
653 gSecretMgr.Clear();
654 gLevelTime = 0;
655 automapping = 1;
656
657 int modernTypesErased = 0;
658 for (int i = 0; i < kMaxSprites; i++)
659 {
660 spritetype *pSprite = &sprite[i];
661 if (pSprite->statnum < kMaxStatus && pSprite->extra > 0) {
662
663 XSPRITE *pXSprite = &xsprite[pSprite->extra];
664 if ((pXSprite->lSkill & (1 << gameOptions->nDifficulty)) || (pXSprite->lS && gameOptions->nGameType == 0)
665 || (pXSprite->lB && gameOptions->nGameType == 2) || (pXSprite->lT && gameOptions->nGameType == 3)
666 || (pXSprite->lC && gameOptions->nGameType == 1)) {
667
668 DeleteSprite(i);
669 continue;
670 }
671
672
673 #ifdef NOONE_EXTENSIONS
674 if (!gModernMap && nnExtEraseModernStuff(pSprite, pXSprite))
675 modernTypesErased++;
676 #endif
677 }
678 }
679
680 #ifdef NOONE_EXTENSIONS
681 if (!gModernMap)
682 OSD_Printf("> Modern types erased: %d.\n", modernTypesErased);
683 #endif
684
685 scrLoadPLUs();
686 startpos.z = getflorzofslope(startsectnum,startpos.x,startpos.y);
687 for (int i = 0; i < kMaxPlayers; i++) {
688 gStartZone[i].x = startpos.x;
689 gStartZone[i].y = startpos.y;
690 gStartZone[i].z = startpos.z;
691 gStartZone[i].sectnum = startsectnum;
692 gStartZone[i].ang = startang;
693
694 #ifdef NOONE_EXTENSIONS
695 // Create spawn zones for players in teams mode.
696 if (gModernMap && i <= kMaxPlayers / 2) {
697 gStartZoneTeam1[i].x = startpos.x;
698 gStartZoneTeam1[i].y = startpos.y;
699 gStartZoneTeam1[i].z = startpos.z;
700 gStartZoneTeam1[i].sectnum = startsectnum;
701 gStartZoneTeam1[i].ang = startang;
702
703 gStartZoneTeam2[i].x = startpos.x;
704 gStartZoneTeam2[i].y = startpos.y;
705 gStartZoneTeam2[i].z = startpos.z;
706 gStartZoneTeam2[i].sectnum = startsectnum;
707 gStartZoneTeam2[i].ang = startang;
708 }
709 #endif
710 }
711 InitSectorFX();
712 warpInit();
713 actInit(false);
714 evInit();
715 for (int i = connecthead; i >= 0; i = connectpoint2[i])
716 {
717 if (!(gameOptions->uGameFlags&1))
718 {
719 if (numplayers == 1)
720 {
721 gProfile[i].skill = gSkill;
722 gProfile[i].nAutoAim = gAutoAim;
723 gProfile[i].nWeaponSwitch = gWeaponSwitch;
724 }
725 playerInit(i,0);
726 }
727 playerStart(i, 1);
728 }
729 if (gameOptions->uGameFlags&1)
730 {
731 for (int i = connecthead; i >= 0; i = connectpoint2[i])
732 {
733 PLAYER *pPlayer = &gPlayer[i];
734 pPlayer->pXSprite->health &= 0xf000;
735 pPlayer->pXSprite->health |= gHealthTemp[i];
736 pPlayer->weaponQav = gPlayerTemp[i].weaponQav;
737 pPlayer->curWeapon = gPlayerTemp[i].curWeapon;
738 pPlayer->weaponState = gPlayerTemp[i].weaponState;
739 pPlayer->weaponAmmo = gPlayerTemp[i].weaponAmmo;
740 pPlayer->qavCallback = gPlayerTemp[i].qavCallback;
741 pPlayer->qavLoop = gPlayerTemp[i].qavLoop;
742 pPlayer->weaponTimer = gPlayerTemp[i].weaponTimer;
743 pPlayer->nextWeapon = gPlayerTemp[i].nextWeapon;
744 }
745 }
746 gameOptions->uGameFlags &= ~3;
747 scrSetDac();
748 PreloadCache();
749 InitMirrors();
750 gFrameClock = 0;
751 trInit();
752 if (!bVanilla && !gMe->packSlots[1].isActive) // if diving suit is not active, turn off reverb sound effect
753 sfxSetReverb(0);
754 ambInit();
755 sub_79760();
756 gCacheMiss = 0;
757 gFrame = 0;
758 gChokeCounter = 0;
759 if (!gDemo.at1)
760 gGameMenuMgr.Deactivate();
761 levelTryPlayMusicOrNothing(gGameOptions.nEpisode, gGameOptions.nLevel);
762 // viewSetMessage("");
763 viewSetErrorMessage("");
764 viewResizeView(gViewSize);
765 if (gGameOptions.nGameType == 3)
766 gGameMessageMgr.SetCoordinates(gViewX0S+1,gViewY0S+15);
767 netWaitForEveryone(0);
768 totalclock = 0;
769 gPaused = 0;
770 gGameStarted = 1;
771 ready2send = 1;
772 }
773
StartNetworkLevel(void)774 void StartNetworkLevel(void)
775 {
776 if (gDemo.at0)
777 gDemo.Close();
778 if (!(gGameOptions.uGameFlags&1))
779 {
780 gGameOptions.nEpisode = gPacketStartGame.episodeId;
781 gGameOptions.nLevel = gPacketStartGame.levelId;
782 gGameOptions.nGameType = gPacketStartGame.gameType;
783 gGameOptions.nDifficulty = gPacketStartGame.difficulty;
784 gGameOptions.nMonsterSettings = gPacketStartGame.monsterSettings;
785 gGameOptions.nWeaponSettings = gPacketStartGame.weaponSettings;
786 gGameOptions.nItemSettings = gPacketStartGame.itemSettings;
787 gGameOptions.nRespawnSettings = gPacketStartGame.respawnSettings;
788 gGameOptions.bFriendlyFire = gPacketStartGame.bFriendlyFire;
789 gGameOptions.bKeepKeysOnRespawn = gPacketStartGame.bKeepKeysOnRespawn;
790
791 ///////
792 gGameOptions.weaponsV10x = gPacketStartGame.weaponsV10x;
793 ///////
794
795 gBlueFlagDropped = false;
796 gRedFlagDropped = false;
797
798 if (gPacketStartGame.userMap)
799 levelAddUserMap(gPacketStartGame.userMapName);
800 else
801 levelSetupOptions(gGameOptions.nEpisode, gGameOptions.nLevel);
802 }
803 StartLevel(&gGameOptions);
804 }
805
806 int gDoQuickSave = 0;
807
DoQuickLoad(void)808 static void DoQuickLoad(void)
809 {
810 if (!gGameMenuMgr.m_bActive)
811 {
812 if (gQuickLoadSlot != -1)
813 {
814 QuickLoadGame();
815 return;
816 }
817 if (gQuickLoadSlot == -1 && gQuickSaveSlot != -1)
818 {
819 gQuickLoadSlot = gQuickSaveSlot;
820 QuickLoadGame();
821 return;
822 }
823 gGameMenuMgr.Push(&menuLoadGame,-1);
824 }
825 }
826
DoQuickSave(void)827 static void DoQuickSave(void)
828 {
829 if (gGameStarted && !gGameMenuMgr.m_bActive && gPlayer[myconnectindex].pXSprite->health != 0)
830 {
831 if (gQuickSaveSlot != -1)
832 {
833 QuickSaveGame();
834 return;
835 }
836 gGameMenuMgr.Push(&menuSaveGame,-1);
837 }
838 }
839
LocalKeys(void)840 void LocalKeys(void)
841 {
842 char alt = keystatus[sc_LeftAlt] | keystatus[sc_RightAlt];
843 char ctrl = keystatus[sc_LeftControl] | keystatus[sc_RightControl];
844 char shift = keystatus[sc_LeftShift] | keystatus[sc_RightShift];
845 if (BUTTON(gamefunc_See_Chase_View) && !alt && !shift)
846 {
847 CONTROL_ClearButton(gamefunc_See_Chase_View);
848 if (gViewPos > VIEWPOS_0)
849 gViewPos = VIEWPOS_0;
850 else
851 gViewPos = VIEWPOS_1;
852 }
853 if (BUTTON(gamefunc_See_Coop_View))
854 {
855 CONTROL_ClearButton(gamefunc_See_Coop_View);
856 if (gGameOptions.nGameType == 1)
857 {
858 gViewIndex = connectpoint2[gViewIndex];
859 if (gViewIndex == -1)
860 gViewIndex = connecthead;
861 gView = &gPlayer[gViewIndex];
862 }
863 else if (gGameOptions.nGameType == 3)
864 {
865 int oldViewIndex = gViewIndex;
866 do
867 {
868 gViewIndex = connectpoint2[gViewIndex];
869 if (gViewIndex == -1)
870 gViewIndex = connecthead;
871 if (oldViewIndex == gViewIndex || gMe->teamId == gPlayer[gViewIndex].teamId)
872 break;
873 } while (oldViewIndex != gViewIndex);
874 gView = &gPlayer[gViewIndex];
875 }
876 }
877 if (gDoQuickSave)
878 {
879 keyFlushScans();
880 switch (gDoQuickSave)
881 {
882 case 1:
883 DoQuickSave();
884 break;
885 case 2:
886 DoQuickLoad();
887 break;
888 }
889 gDoQuickSave = 0;
890 return;
891 }
892 char key;
893 if ((key = keyGetScan()) != 0)
894 {
895 if ((alt || shift) && gGameOptions.nGameType > 0 && key >= sc_F1 && key <= sc_F10)
896 {
897 char fk = key - sc_F1;
898 if (alt)
899 {
900 netBroadcastTaunt(myconnectindex, fk);
901 }
902 else
903 {
904 gPlayerMsg.Set(CommbatMacro[fk]);
905 gPlayerMsg.Send();
906 }
907 keyFlushScans();
908 keystatus[key] = 0;
909 CONTROL_ClearButton(gamefunc_See_Chase_View);
910 return;
911 }
912 switch (key)
913 {
914 case sc_kpad_Period:
915 case sc_Delete:
916 if (ctrl && alt)
917 {
918 gQuitGame = 1;
919 return;
920 }
921 break;
922 case sc_Escape:
923 keyFlushScans();
924 if (gGameStarted && gPlayer[myconnectindex].pXSprite->health != 0)
925 {
926 if (!gGameMenuMgr.m_bActive)
927 gGameMenuMgr.Push(&menuMainWithSave,-1);
928 }
929 else
930 {
931 if (!gGameMenuMgr.m_bActive)
932 gGameMenuMgr.Push(&menuMain,-1);
933 }
934 return;
935 case sc_F1:
936 keyFlushScans();
937 if (gGameOptions.nGameType == 0)
938 gGameMenuMgr.Push(&menuOrder,-1);
939 break;
940 case sc_F2:
941 keyFlushScans();
942 if (!gGameMenuMgr.m_bActive && gGameOptions.nGameType == 0)
943 gGameMenuMgr.Push(&menuSaveGame,-1);
944 break;
945 case sc_F3:
946 keyFlushScans();
947 if (!gGameMenuMgr.m_bActive && gGameOptions.nGameType == 0)
948 gGameMenuMgr.Push(&menuLoadGame,-1);
949 break;
950 case sc_F4:
951 keyFlushScans();
952 if (!gGameMenuMgr.m_bActive)
953 gGameMenuMgr.Push(&menuOptionsSound,-1);
954 return;
955 case sc_F5:
956 keyFlushScans();
957 if (!gGameMenuMgr.m_bActive)
958 gGameMenuMgr.Push(&menuOptions,-1);
959 return;
960 case sc_F6:
961 keyFlushScans();
962 DoQuickSave();
963 break;
964 case sc_F8:
965 keyFlushScans();
966 if (!gGameMenuMgr.m_bActive)
967 gGameMenuMgr.Push(&menuOptionsDisplayMode, -1);
968 return;
969 case sc_F9:
970 keyFlushScans();
971 DoQuickLoad();
972 break;
973 case sc_F10:
974 keyFlushScans();
975 if (!gGameMenuMgr.m_bActive)
976 gGameMenuMgr.Push(&menuQuit,-1);
977 break;
978 case sc_F11:
979 break;
980 case sc_F12:
981 videoCaptureScreen("blud0000.tga", 0);
982 break;
983 }
984 }
985 }
986
987 bool gRestartGame = false;
988
ProcessFrame(void)989 void ProcessFrame(void)
990 {
991 char buffer[128];
992 for (int i = connecthead; i >= 0; i = connectpoint2[i])
993 {
994 gPlayer[i].input.buttonFlags = gFifoInput[gNetFifoTail&255][i].buttonFlags;
995 gPlayer[i].input.keyFlags.word |= gFifoInput[gNetFifoTail&255][i].keyFlags.word;
996 gPlayer[i].input.useFlags.byte |= gFifoInput[gNetFifoTail&255][i].useFlags.byte;
997 if (gFifoInput[gNetFifoTail&255][i].newWeapon)
998 gPlayer[i].input.newWeapon = gFifoInput[gNetFifoTail&255][i].newWeapon;
999 gPlayer[i].input.forward = gFifoInput[gNetFifoTail&255][i].forward;
1000 gPlayer[i].input.q16turn = gFifoInput[gNetFifoTail&255][i].q16turn;
1001 gPlayer[i].input.strafe = gFifoInput[gNetFifoTail&255][i].strafe;
1002 gPlayer[i].input.q16mlook = gFifoInput[gNetFifoTail&255][i].q16mlook;
1003 }
1004 gNetFifoTail++;
1005 if (!(gFrame&7))
1006 {
1007 CalcGameChecksum();
1008 memcpy(gCheckFifo[gCheckHead[myconnectindex]&255][myconnectindex], gChecksum, sizeof(gChecksum));
1009 gCheckHead[myconnectindex]++;
1010 }
1011 for (int i = connecthead; i >= 0; i = connectpoint2[i])
1012 {
1013 if (gPlayer[i].input.keyFlags.quit)
1014 {
1015 gPlayer[i].input.keyFlags.quit = 0;
1016 netBroadcastPlayerLogoff(i);
1017 if (i == myconnectindex)
1018 {
1019 // netBroadcastMyLogoff(gQuitRequest == 2);
1020 gQuitGame = true;
1021 gRestartGame = gQuitRequest == 2;
1022 netDeinitialize();
1023 netResetToSinglePlayer();
1024 return;
1025 }
1026 }
1027 if (gPlayer[i].input.keyFlags.restart)
1028 {
1029 gPlayer[i].input.keyFlags.restart = 0;
1030 levelRestart();
1031 return;
1032 }
1033 if (gPlayer[i].input.keyFlags.pause)
1034 {
1035 gPlayer[i].input.keyFlags.pause = 0;
1036 gPaused = !gPaused;
1037 if (gPaused && gGameOptions.nGameType > 0 && numplayers > 1)
1038 {
1039 sprintf(buffer,"%s paused the game",gProfile[i].name);
1040 viewSetMessage(buffer);
1041 }
1042 }
1043 }
1044 viewClearInterpolations();
1045 if (!gDemo.at1)
1046 {
1047 if (gPaused || gEndGameMgr.at0 || (gGameOptions.nGameType == 0 && gGameMenuMgr.m_bActive))
1048 return;
1049 if (gDemo.at0)
1050 gDemo.Write(gFifoInput[(gNetFifoTail-1)&255]);
1051 }
1052 for (int i = connecthead; i >= 0; i = connectpoint2[i])
1053 {
1054 viewBackupView(i);
1055 playerProcess(&gPlayer[i]);
1056 }
1057 trProcessBusy();
1058 evProcess((int)gFrameClock);
1059 seqProcess(4);
1060 DoSectorPanning();
1061 actProcessSprites();
1062 actPostProcess();
1063 #ifdef POLYMER
1064 G_RefreshLights();
1065 #endif
1066 viewCorrectPrediction();
1067 sndProcess();
1068 ambProcess();
1069 viewUpdateDelirium();
1070 viewUpdateShake();
1071 sfxUpdate3DSounds();
1072 if (gMe->hand == 1)
1073 {
1074 #define CHOKERATE 8
1075 #define TICRATE 30
1076 gChokeCounter += CHOKERATE;
1077 while (gChokeCounter >= TICRATE)
1078 {
1079 gChoke.at1c(gMe);
1080 gChokeCounter -= TICRATE;
1081 }
1082 }
1083 gLevelTime++;
1084 gFrame++;
1085 gFrameClock += 4;
1086 if ((gGameOptions.uGameFlags&1) != 0 && !gStartNewGame)
1087 {
1088 ready2send = 0;
1089 if (gNetPlayers > 1 && gNetMode == NETWORK_SERVER && gPacketMode == PACKETMODE_1 && myconnectindex == connecthead)
1090 {
1091 while (gNetFifoMasterTail < gNetFifoTail)
1092 {
1093 gameHandleEvents();
1094 netMasterUpdate();
1095 }
1096 }
1097 if (gDemo.at0)
1098 gDemo.Close();
1099 sndFadeSong(4000);
1100 seqKillAll();
1101 if (gGameOptions.uGameFlags&2)
1102 {
1103 if (gGameOptions.nGameType == 0)
1104 {
1105 if (gGameOptions.uGameFlags&8)
1106 levelPlayEndScene(gGameOptions.nEpisode);
1107 gGameMenuMgr.Deactivate();
1108 gGameMenuMgr.Push(&menuCredits,-1);
1109 }
1110 gGameOptions.uGameFlags &= ~3;
1111 gRestartGame = 1;
1112 gQuitGame = 1;
1113 }
1114 else
1115 {
1116 gEndGameMgr.Setup();
1117 viewResizeView(gViewSize);
1118 }
1119 }
1120 }
1121
1122 SWITCH switches[] = {
1123 { "?", 0, 0 },
1124 { "help", 0, 0 },
1125 { "broadcast", 1, 0 },
1126 { "map", 2, 1 },
1127 { "masterslave", 3, 0 },
1128 //{ "net", 4, 1 },
1129 { "nodudes", 5, 1 },
1130 { "playback", 6, 1 },
1131 { "record", 7, 1 },
1132 { "robust", 8, 0 },
1133 { "setupfile", 9, 1 },
1134 { "skill", 10, 1 },
1135 //{ "nocd", 11, 0 },
1136 //{ "8250", 12, 0 },
1137 { "ini", 13, 1 },
1138 { "noaim", 14, 0 },
1139 //{ "f", 15, 1 },
1140 { "control", 16, 1 },
1141 { "vector", 17, 1 },
1142 { "quick", 18, 0 },
1143 //{ "getopt", 19, 1 },
1144 //{ "auto", 20, 1 },
1145 { "pname", 21, 1 },
1146 { "noresend", 22, 0 },
1147 { "silentaim", 23, 0 },
1148 { "nodemo", 25, 0 },
1149 { "art", 26, 1 },
1150 { "snd", 27, 1 },
1151 { "rff", 28, 1 },
1152 #ifdef USE_QHEAP
1153 { "maxalloc", 29, 1 },
1154 #endif
1155 { "server", 30, 1 },
1156 { "client", 31, 1 },
1157 { "noautoload", 32, 0 },
1158 { "usecwd", 33, 0 },
1159 { "cachesize", 34, 1 },
1160 { "g", 35, 1 },
1161 { "grp", 35, 1 },
1162 { "game_dir", 36, 1 },
1163 { "cfg", 9, 1 },
1164 { "setup", 37, 0 },
1165 { "nosetup", 38, 0 },
1166 { "port", 39, 1 },
1167 { "h", 40, 1 },
1168 { "mh", 41, 1 },
1169 { "j", 42, 1 },
1170 { "c", 43, 1 },
1171 { "conf", 43, 1 },
1172 { "noconsole", 43, 0 },
1173 { NULL, 0, 0 }
1174 };
1175
PrintHelp(void)1176 void PrintHelp(void)
1177 {
1178 char tempbuf[128];
1179 static char const s[] = "Usage: " APPBASENAME " [files] [options]\n"
1180 "Example: " APPBASENAME " -usecwd -cfg myconfig.cfg -map nukeland.map\n\n"
1181 "Files can be of type [grp|zip|map|def]\n"
1182 "\n"
1183 "-art [file.art]\tSpecify an art base file name\n"
1184 "-cachesize #\tSet cache size in kB\n"
1185 "-cfg [file.cfg]\tUse an alternate configuration file\n"
1186 "-client [host]\tConnect to a multiplayer game\n"
1187 "-game_dir [dir]\tSpecify game data directory\n"
1188 "-g [file.grp]\tLoad additional game data\n"
1189 "-h [file.def]\tLoad an alternate definitions file\n"
1190 "-ini [file.ini]\tSpecify an INI file name (default is blood.ini)\n"
1191 "-j [dir]\t\tAdd a directory to " APPNAME "'s search list\n"
1192 "-map [file.map]\tLoad an external map file\n"
1193 "-mh [file.def]\tInclude an additional definitions module\n"
1194 "-noautoload\tDisable loading from autoload directory\n"
1195 "-nodemo\t\tNo Demos\n"
1196 "-nodudes\tNo monsters\n"
1197 "-playback\tPlay back a demo\n"
1198 "-pname\t\tOverride player name setting from config file\n"
1199 "-record\t\tRecord demo\n"
1200 "-rff\t\tSpecify an RFF file for Blood game resources\n"
1201 "-server [players]\tStart a multiplayer server\n"
1202 #ifdef STARTUP_SETUP_WINDOW
1203 "-setup/nosetup\tEnable or disable startup window\n"
1204 #endif
1205 "-skill\t\tSet player handicap; Range:0..4; Default:2; (NOT difficulty level.)\n"
1206 "-snd\t\tSpecify an RFF Sound file name\n"
1207 "-usecwd\t\tRead data and configuration from current directory\n"
1208 ;
1209 #ifdef WM_MSGBOX_WINDOW
1210 Bsnprintf(tempbuf, sizeof(tempbuf), APPNAME " %s", s_buildRev);
1211 wm_msgbox(tempbuf, s);
1212 #else
1213 initprintf("%s\n", s);
1214 #endif
1215 #if 0
1216 puts("Blood Command-line Options:");
1217 // NUKE-TODO:
1218 puts("-? This help");
1219 //puts("-8250 Enforce obsolete UART I/O");
1220 //puts("-auto Automatic Network start. Implies -quick");
1221 //puts("-getopt Use network game options from file. Implies -auto");
1222 puts("-broadcast Set network to broadcast packet mode");
1223 puts("-masterslave Set network to master/slave packet mode");
1224 //puts("-net Net mode game");
1225 //puts("-noaim Disable auto-aiming");
1226 //puts("-nocd Disable CD audio");
1227 puts("-nodudes No monsters");
1228 puts("-nodemo No Demos");
1229 puts("-robust Robust network sync checking");
1230 puts("-skill Set player handicap; Range:0..4; Default:2; (NOT difficulty level.)");
1231 puts("-quick Skip Intro screens and get right to the game");
1232 puts("-pname Override player name setting from config file");
1233 puts("-map Specify a user map");
1234 puts("-playback Play back a demo");
1235 puts("-record Record a demo");
1236 puts("-art Specify an art base file name");
1237 puts("-snd Specify an RFF Sound file name");
1238 puts("-RFF Specify an RFF file for Blood game resources");
1239 puts("-ini Specify an INI file name (default is blood.ini)");
1240 #endif
1241 exit(0);
1242 }
1243
ParseOptions(void)1244 void ParseOptions(void)
1245 {
1246 int option;
1247 while ((option = GetOptions(switches)) != -1)
1248 {
1249 switch (option)
1250 {
1251 case -3:
1252 ThrowError("Invalid argument: %s", OptFull);
1253 fallthrough__;
1254 case 29:
1255 #ifdef USE_QHEAP
1256 if (OptArgc < 1)
1257 ThrowError("Missing argument");
1258 nMaxAlloc = atoi(OptArgv[0]);
1259 if (!nMaxAlloc)
1260 nMaxAlloc = 0x2000000;
1261 break;
1262 #endif
1263 case 0:
1264 PrintHelp();
1265 break;
1266 //case 19:
1267 // byte_148eec = 1;
1268 //case 20:
1269 // if (OptArgc < 1)
1270 // ThrowError("Missing argument");
1271 // strncpy(byte_148ef0, OptArgv[0], 13);
1272 // byte_148ef0[12] = 0;
1273 // bQuickStart = 1;
1274 // byte_148eeb = 1;
1275 // if (gGameOptions.gameType == 0)
1276 // gGameOptions.gameType = 2;
1277 // break;
1278 case 25:
1279 bNoDemo = 1;
1280 break;
1281 case 18:
1282 bQuickStart = 1;
1283 break;
1284 //case 12:
1285 // EightyTwoFifty = 1;
1286 // break;
1287 case 1:
1288 gPacketMode = PACKETMODE_2;
1289 break;
1290 case 21:
1291 if (OptArgc < 1)
1292 ThrowError("Missing argument");
1293 strcpy(gPName, OptArgv[0]);
1294 bCustomName = 1;
1295 break;
1296 case 2:
1297 if (OptArgc < 1)
1298 ThrowError("Missing argument");
1299 strcpy(gUserMapFilename, OptArgv[0]);
1300 bAddUserMap = 1;
1301 bNoDemo = 1;
1302 break;
1303 case 3:
1304 gPacketMode = PACKETMODE_2;
1305 break;
1306 case 4:
1307 //if (OptArgc < 1)
1308 // ThrowError("Missing argument");
1309 //if (gGameOptions.nGameType == 0)
1310 // gGameOptions.nGameType = 2;
1311 break;
1312 case 30:
1313 if (OptArgc < 1)
1314 ThrowError("Missing argument");
1315 gNetPlayers = ClipRange(atoi(OptArgv[0]), 1, kMaxPlayers);
1316 gNetMode = NETWORK_SERVER;
1317 break;
1318 case 31:
1319 if (OptArgc < 1)
1320 ThrowError("Missing argument");
1321 gNetMode = NETWORK_CLIENT;
1322 strncpy(gNetAddress, OptArgv[0], sizeof(gNetAddress)-1);
1323 break;
1324 case 14:
1325 gAutoAim = 0;
1326 break;
1327 case 22:
1328 bNoResend = 0;
1329 break;
1330 case 23:
1331 bSilentAim = 1;
1332 break;
1333 case 5:
1334 gGameOptions.nMonsterSettings = 0;
1335 break;
1336 case 6:
1337 if (OptArgc < 1)
1338 gDemo.SetupPlayback(NULL);
1339 else
1340 gDemo.SetupPlayback(OptArgv[0]);
1341 break;
1342 case 7:
1343 if (OptArgc < 1)
1344 gDemo.Create(NULL);
1345 else
1346 gDemo.Create(OptArgv[0]);
1347 break;
1348 case 8:
1349 gRobust = 1;
1350 break;
1351 case 13:
1352 if (OptArgc < 1)
1353 ThrowError("Missing argument");
1354 levelOverrideINI(OptArgv[0]);
1355 bNoDemo = 1;
1356 break;
1357 case 26:
1358 if (OptArgc < 1)
1359 ThrowError("Missing argument");
1360 pUserTiles = (char*)malloc(strlen(OptArgv[0])+1);
1361 if (!pUserTiles)
1362 return;
1363 strcpy(pUserTiles, OptArgv[0]);
1364 break;
1365 case 27:
1366 if (OptArgc < 1)
1367 ThrowError("Missing argument");
1368 pUserSoundRFF = (char*)malloc(strlen(OptArgv[0])+1);
1369 if (!pUserSoundRFF)
1370 return;
1371 strcpy(pUserSoundRFF, OptArgv[0]);
1372 break;
1373 case 28:
1374 if (OptArgc < 1)
1375 ThrowError("Missing argument");
1376 pUserRFF = (char*)malloc(strlen(OptArgv[0])+1);
1377 if (!pUserRFF)
1378 return;
1379 strcpy(pUserRFF, OptArgv[0]);
1380 break;
1381 case 9:
1382 if (OptArgc < 1)
1383 ThrowError("Missing argument");
1384 strcpy(SetupFilename, OptArgv[0]);
1385 break;
1386 case 10:
1387 if (OptArgc < 1)
1388 ThrowError("Missing argument");
1389 gSkill = strtoul(OptArgv[0], NULL, 0);
1390 if (gSkill < 0)
1391 gSkill = 0;
1392 else if (gSkill > 4)
1393 gSkill = 4;
1394 break;
1395 case 15:
1396 break;
1397 case -2:
1398 {
1399 const char *k = strrchr(OptFull, '.');
1400 if (k)
1401 {
1402 if (!Bstrcasecmp(k, ".map"))
1403 {
1404 strcpy(gUserMapFilename, OptFull);
1405 bAddUserMap = 1;
1406 bNoDemo = 1;
1407 }
1408 else if (!Bstrcasecmp(k, ".grp") || !Bstrcasecmp(k, ".zip") || !Bstrcasecmp(k, ".pk3") || !Bstrcasecmp(k, ".pk4"))
1409 {
1410 G_AddGroup(OptFull);
1411 }
1412 else if (!Bstrcasecmp(k, ".def"))
1413 {
1414 clearDefNamePtr();
1415 g_defNamePtr = dup_filename(OptFull);
1416 initprintf("Using DEF file \"%s\".\n", g_defNamePtr);
1417 continue;
1418 }
1419 }
1420 else
1421 {
1422 strcpy(gUserMapFilename, OptFull);
1423 bAddUserMap = 1;
1424 bNoDemo = 1;
1425 }
1426 break;
1427 }
1428 case 11:
1429 //bNoCDAudio = 1;
1430 break;
1431 case 32:
1432 initprintf("Autoload disabled\n");
1433 bNoAutoLoad = true;
1434 break;
1435 case 33:
1436 g_useCwd = true;
1437 break;
1438 case 34:
1439 {
1440 if (OptArgc < 1)
1441 ThrowError("Missing argument");
1442 uint32_t j = strtoul(OptArgv[0], NULL, 0);
1443 MAXCACHE1DSIZE = j<<10;
1444 initprintf("Cache size: %dkB\n", j);
1445 break;
1446 }
1447 case 35:
1448 if (OptArgc < 1)
1449 ThrowError("Missing argument");
1450 G_AddGroup(OptArgv[0]);
1451 break;
1452 case 36:
1453 if (OptArgc < 1)
1454 ThrowError("Missing argument");
1455 Bstrncpyz(g_modDir, OptArgv[0], sizeof(g_modDir));
1456 G_AddPath(OptArgv[0]);
1457 break;
1458 case 37:
1459 gCommandSetup = true;
1460 break;
1461 case 38:
1462 gNoSetup = true;
1463 gCommandSetup = false;
1464 break;
1465 case 39:
1466 if (OptArgc < 1)
1467 ThrowError("Missing argument");
1468 gNetPort = strtoul(OptArgv[0], NULL, 0);
1469 break;
1470 case 40:
1471 if (OptArgc < 1)
1472 ThrowError("Missing argument");
1473 G_AddDef(OptArgv[0]);
1474 break;
1475 case 41:
1476 if (OptArgc < 1)
1477 ThrowError("Missing argument");
1478 G_AddDefModule(OptArgv[0]);
1479 break;
1480 case 42:
1481 if (OptArgc < 1)
1482 ThrowError("Missing argument");
1483 G_AddPath(OptArgv[0]);
1484 break;
1485 case 43: // conf, noconsole
1486 break;
1487 }
1488 }
1489 #if 0
1490 if (bAddUserMap)
1491 {
1492 char zNode[BMAX_PATH];
1493 char zDir[BMAX_PATH];
1494 char zFName[BMAX_PATH];
1495 _splitpath(gUserMapFilename, zNode, zDir, zFName, NULL);
1496 strcpy(g_modDir, zNode);
1497 strcat(g_modDir, zDir);
1498 strcpy(gUserMapFilename, zFName);
1499 }
1500 #endif
1501 }
1502
ClockStrobe()1503 void ClockStrobe()
1504 {
1505 //gGameClock++;
1506 }
1507
1508 #if defined(_WIN32) && defined(DEBUGGINGAIDS)
1509 // See FILENAME_CASE_CHECK in cache1d.c
check_filename_casing(void)1510 static int32_t check_filename_casing(void)
1511 {
1512 return 1;
1513 }
1514 #endif
1515
app_main(int argc,char const * const * argv)1516 int app_main(int argc, char const * const * argv)
1517 {
1518 char buffer[BMAX_PATH];
1519 margc = argc;
1520 margv = argv;
1521 #ifdef _WIN32
1522 #ifndef DEBUGGINGAIDS
1523 if (!G_CheckCmdSwitch(argc, argv, "-noinstancechecking") && !windowsCheckAlreadyRunning())
1524 {
1525 #ifdef EDUKE32_STANDALONE
1526 if (!wm_ynbox(APPNAME, "It looks like " APPNAME " is already running.\n\n"
1527 #else
1528 if (!wm_ynbox(APPNAME, "It looks like the game is already running.\n\n"
1529 #endif
1530 "Are you sure you want to start another copy?"))
1531 return 3;
1532 }
1533 #endif
1534
1535 win_priorityclass = 0;
1536
1537 G_ExtPreInit(argc, argv);
1538
1539 #ifdef DEBUGGINGAIDS
1540 extern int32_t (*check_filename_casing_fn)(void);
1541 check_filename_casing_fn = check_filename_casing;
1542 #endif
1543 #endif
1544
1545 #ifdef __APPLE__
1546 if (!g_useCwd)
1547 {
1548 char cwd[BMAX_PATH];
1549 char *homedir = Bgethomedir();
1550 if (homedir)
1551 Bsnprintf(cwd, sizeof(cwd), "%s/Library/Logs/" APPBASENAME ".log", homedir);
1552 else
1553 Bstrcpy(cwd, APPBASENAME ".log");
1554 OSD_SetLogFile(cwd);
1555 Xfree(homedir);
1556 }
1557 else
1558 #endif
1559 OSD_SetLogFile(APPBASENAME ".log");
1560
1561 OSD_SetFunctions(NULL,
1562 NULL,
1563 NULL,
1564 NULL,
1565 NULL,
1566 GAME_clearbackground,
1567 BGetTime,
1568 GAME_onshowosd);
1569
1570 wm_setapptitle(APPNAME);
1571
1572 initprintf(APPNAME " %s\n", s_buildRev);
1573 PrintBuildInfo();
1574
1575 memcpy(&gGameOptions, &gSingleGameOptions, sizeof(GAMEOPTIONS));
1576 ParseOptions();
1577 G_ExtInit();
1578
1579 if (!g_useCwd)
1580 G_AddSearchPaths();
1581
1582 // used with binds for fast function lookup
1583 hash_init(&h_gamefuncs);
1584 for (bssize_t i=NUMGAMEFUNCTIONS-1; i>=0; i--)
1585 {
1586 if (gamefunctions[i][0] == '\0')
1587 continue;
1588
1589 char *str = Bstrtolower(Xstrdup(gamefunctions[i]));
1590 hash_add(&h_gamefuncs,gamefunctions[i],i,0);
1591 hash_add(&h_gamefuncs,str,i,0);
1592 Bfree(str);
1593 }
1594
1595 #ifdef STARTUP_SETUP_WINDOW
1596 int const readSetup =
1597 #endif
1598 CONFIG_ReadSetup();
1599 if (bCustomName)
1600 strcpy(szPlayerName, gPName);
1601
1602 if (enginePreInit())
1603 {
1604 #ifdef WM_MSGBOX_WINDOW
1605 wm_msgbox("Build Engine Initialization Error",
1606 "There was a problem initializing the Build engine: %s", engineerrstr);
1607 #endif
1608 ERRprintf("app_main: There was a problem initializing the Build engine: %s\n", engineerrstr);
1609 Bexit(2);
1610 }
1611
1612 if (Bstrcmp(SetupFilename, SETUPFILENAME))
1613 initprintf("Using config file \"%s\".\n", SetupFilename);
1614
1615 ScanINIFiles();
1616
1617 #ifdef STARTUP_SETUP_WINDOW
1618 if (readSetup < 0 || (!gNoSetup && (configversion != BYTEVERSION || gSetup.forcesetup)) || gCommandSetup)
1619 {
1620 if (quitevent || !startwin_run())
1621 {
1622 engineUnInit();
1623 Bexit(0);
1624 }
1625 }
1626 #endif
1627
1628 G_LoadGroups(!bNoAutoLoad && !gSetup.noautoload);
1629
1630 //if (!g_useCwd)
1631 // G_CleanupSearchPaths();
1632
1633 initprintf("Initializing OSD...\n");
1634
1635 //Bsprintf(tempbuf, HEAD2 " %s", s_buildRev);
1636 OSD_SetVersion("Blood", 10, 0);
1637 OSD_SetParameters(0, 0, 0, 12, 2, 12, OSD_ERROR, OSDTEXT_RED, gamefunctions[gamefunc_Show_Console][0] == '\0' ? OSD_PROTECTED : 0);
1638 registerosdcommands();
1639
1640 char *const setupFileName = Xstrdup(SetupFilename);
1641 char *const p = strtok(setupFileName, ".");
1642
1643 if (!p || !Bstrcmp(SetupFilename, SETUPFILENAME))
1644 Bsprintf(buffer, "settings.cfg");
1645 else
1646 Bsprintf(buffer, "%s_settings.cfg", p);
1647
1648 Bfree(setupFileName);
1649
1650 OSD_Exec(buffer);
1651
1652 // Not neccessary ?
1653 // CONFIG_SetDefaultKeys(keydefaults, true);
1654
1655 system_getcvars();
1656
1657 #ifdef USE_QHEAP
1658 Resource::heap = new QHeap(nMaxAlloc);
1659 #endif
1660 gSysRes.Init(pUserRFF ? pUserRFF : "BLOOD.RFF");
1661 gGuiRes.Init("GUI.RFF");
1662 gSoundRes.Init(pUserSoundRFF ? pUserSoundRFF : "SOUNDS.RFF");
1663
1664 HookReplaceFunctions();
1665
1666 initprintf("Initializing Build 3D engine\n");
1667 scrInit();
1668
1669 initprintf("Creating standard color lookups\n");
1670 scrCreateStdColors();
1671
1672 initprintf("Loading tiles\n");
1673 if (pUserTiles)
1674 {
1675 strcpy(buffer,pUserTiles);
1676 strcat(buffer,"%03i.ART");
1677 if (!tileInit(0,buffer))
1678 ThrowError("User specified ART files not found");
1679 }
1680 else
1681 {
1682 if (!tileInit(0,NULL))
1683 ThrowError("TILES###.ART files not found");
1684 }
1685
1686 LoadExtraArts();
1687
1688 levelLoadDefaults();
1689
1690 loaddefinitionsfile(BLOODWIDESCREENDEF);
1691 loaddefinitions_game(BLOODWIDESCREENDEF, FALSE);
1692
1693 const char *defsfile = G_DefFile();
1694 uint32_t stime = timerGetTicks();
1695 if (!loaddefinitionsfile(defsfile))
1696 {
1697 uint32_t etime = timerGetTicks();
1698 initprintf("Definitions file \"%s\" loaded in %d ms.\n", defsfile, etime-stime);
1699 }
1700 loaddefinitions_game(defsfile, FALSE);
1701 powerupInit();
1702 initprintf("Loading cosine table\n");
1703 trigInit(gSysRes);
1704 initprintf("Initializing view subsystem\n");
1705 viewInit();
1706 initprintf("Initializing dynamic fire\n");
1707 FireInit();
1708 initprintf("Initializing weapon animations\n");
1709 WeaponInit();
1710 LoadSaveSetup();
1711 LoadSavedInfo();
1712 gDemo.LoadDemoInfo();
1713 initprintf("There are %d demo(s) in the loop\n", gDemo.at59ef);
1714 initprintf("Loading control setup\n");
1715 ctrlInit();
1716 timerInit(120);
1717 timerSetCallback(ClockStrobe);
1718 // PORT-TODO: CD audio init
1719
1720 initprintf("Initializing network users\n");
1721 netInitialize(true);
1722 scrSetGameMode(gSetup.fullscreen, gSetup.xdim, gSetup.ydim, gSetup.bpp);
1723 scrSetGamma(gGamma);
1724 viewResizeView(gViewSize);
1725 initprintf("Initializing sound system\n");
1726 sndInit();
1727 sfxInit();
1728 gChoke.sub_83ff0(518, sub_84230);
1729 if (bAddUserMap)
1730 {
1731 levelAddUserMap(gUserMapFilename);
1732 gStartNewGame = 1;
1733 }
1734 SetupMenus();
1735 videoSetViewableArea(0, 0, xdim - 1, ydim - 1);
1736
1737 OSD_Exec("autoexec.cfg");
1738
1739 if (!bQuickStart)
1740 credLogosDos();
1741 scrSetDac();
1742 RESTART:
1743 sub_79760();
1744 gViewIndex = myconnectindex;
1745 gMe = gView = &gPlayer[myconnectindex];
1746 netBroadcastPlayerInfo(myconnectindex);
1747 initprintf("Waiting for network players!\n");
1748 netWaitForEveryone(0);
1749 if (gRestartGame)
1750 {
1751 // Network error
1752 gQuitGame = false;
1753 gRestartGame = false;
1754 netDeinitialize();
1755 netResetToSinglePlayer();
1756 goto RESTART;
1757 }
1758 UpdateNetworkMenus();
1759 if (!gDemo.at0 && gDemo.at59ef > 0 && gGameOptions.nGameType == 0 && !bNoDemo)
1760 gDemo.SetupPlayback(NULL);
1761 viewSetCrosshairColor(CrosshairColors.r, CrosshairColors.g, CrosshairColors.b);
1762 gQuitGame = 0;
1763 gRestartGame = 0;
1764 if (gGameOptions.nGameType > 0)
1765 {
1766 KB_ClearKeysDown();
1767 KB_FlushKeyboardQueue();
1768 keyFlushScans();
1769 }
1770 else if (gDemo.at1 && !bAddUserMap && !bNoDemo)
1771 gDemo.Playback();
1772 if (gDemo.at59ef > 0)
1773 gGameMenuMgr.Deactivate();
1774 if (!bAddUserMap && !gGameStarted)
1775 {
1776 gGameMenuMgr.Push(&menuMain, -1);
1777 if (gGameOptions.nGameType > 0)
1778 gGameMenuMgr.Push(&menuNetStart, 1);
1779 }
1780 ready2send = 1;
1781 static bool frameJustDrawn;
1782 while (!gQuitGame)
1783 {
1784 bool bDraw;
1785 if (gGameStarted)
1786 {
1787 char gameUpdate = false;
1788 double const gameUpdateStartTime = timerGetHiTicks();
1789 while (gPredictTail < gNetFifoHead[myconnectindex] && !gPaused)
1790 {
1791 viewUpdatePrediction(&gFifoInput[gPredictTail&255][myconnectindex]);
1792 }
1793 if (numplayers == 1)
1794 gBufferJitter = 0;
1795 if (totalclock >= gNetFifoClock && ready2send)
1796 {
1797 do
1798 {
1799 if (!frameJustDrawn)
1800 break;
1801 frameJustDrawn = false;
1802 gNetInput = gInput;
1803 gInput = {};
1804 do
1805 {
1806 netGetInput();
1807 gNetFifoClock += 4;
1808 while (gNetFifoHead[myconnectindex]-gNetFifoTail > gBufferJitter && !gStartNewGame && !gQuitGame)
1809 {
1810 int i;
1811 for (i = connecthead; i >= 0; i = connectpoint2[i])
1812 if (gNetFifoHead[i] == gNetFifoTail)
1813 break;
1814 if (i >= 0)
1815 break;
1816 faketimerhandler();
1817 ProcessFrame();
1818 }
1819 } while (totalclock >= gNetFifoClock && ready2send);
1820 gameUpdate = true;
1821 } while (0);
1822 }
1823 if (gameUpdate)
1824 {
1825 g_gameUpdateTime = timerGetHiTicks() - gameUpdateStartTime;
1826 if (g_gameUpdateAvgTime < 0.f)
1827 g_gameUpdateAvgTime = g_gameUpdateTime;
1828 g_gameUpdateAvgTime = ((GAMEUPDATEAVGTIMENUMSAMPLES-1.f)*g_gameUpdateAvgTime+g_gameUpdateTime)/((float) GAMEUPDATEAVGTIMENUMSAMPLES);
1829 }
1830 bDraw = engineFPSLimit() != 0;
1831 if (gQuitRequest && gQuitGame)
1832 videoClearScreen(0);
1833 else
1834 {
1835 netCheckSync();
1836 if (bDraw)
1837 {
1838 viewDrawScreen();
1839 g_gameUpdateAndDrawTime = timerGetHiTicks() - gameUpdateStartTime;
1840 }
1841 }
1842 }
1843 else
1844 {
1845 bDraw = engineFPSLimit() != 0;
1846 if (bDraw)
1847 {
1848 videoClearScreen(0);
1849 rotatesprite(160<<16,100<<16,65536,0,2518,0,0,0x4a,0,0,xdim-1,ydim-1);
1850 }
1851 if (gQuitRequest && !gQuitGame)
1852 netBroadcastMyLogoff(gQuitRequest == 2);
1853 }
1854 if (bDraw)
1855 {
1856 if (gameHandleEvents() && quitevent)
1857 {
1858 KB_KeyDown[sc_Escape] = 1;
1859 quitevent = 0;
1860 }
1861 MUSIC_Update();
1862 CONTROL_BindsEnabled = gInputMode == INPUT_MODE_0;
1863 switch (gInputMode)
1864 {
1865 case INPUT_MODE_1:
1866 if (gGameMenuMgr.m_bActive)
1867 gGameMenuMgr.Process();
1868 break;
1869 case INPUT_MODE_0:
1870 LocalKeys();
1871 break;
1872 default:
1873 break;
1874 }
1875 if (gQuitGame)
1876 continue;
1877
1878 OSD_DispatchQueued();
1879
1880 ctrlGetInput();
1881
1882 switch (gInputMode)
1883 {
1884 case INPUT_MODE_1:
1885 if (gGameMenuMgr.m_bActive)
1886 gGameMenuMgr.Draw();
1887 break;
1888 case INPUT_MODE_2:
1889 gPlayerMsg.ProcessKeys();
1890 gPlayerMsg.Draw();
1891 break;
1892 case INPUT_MODE_3:
1893 gEndGameMgr.ProcessKeys();
1894 gEndGameMgr.Draw();
1895 break;
1896 default:
1897 break;
1898 }
1899 frameJustDrawn = true;
1900 videoNextPage();
1901 }
1902 //scrNextPage();
1903 if (TestBitString(gotpic, 2342))
1904 {
1905 FireProcess();
1906 ClearBitString(gotpic, 2342);
1907 }
1908 //if (byte_148e29 && gStartNewGame)
1909 //{
1910 // gStartNewGame = 0;
1911 // gQuitGame = 1;
1912 //}
1913 if (gStartNewGame)
1914 StartLevel(&gGameOptions);
1915 }
1916 ready2send = 0;
1917 if (gDemo.at0)
1918 gDemo.Close();
1919 if (gRestartGame)
1920 {
1921 UpdateDacs(0, true);
1922 sndStopSong();
1923 FX_StopAllSounds();
1924 gQuitGame = 0;
1925 gQuitRequest = 0;
1926 gRestartGame = 0;
1927 gGameStarted = 0;
1928 levelSetupOptions(0,0);
1929 while (gGameMenuMgr.m_bActive)
1930 {
1931 gGameMenuMgr.Process();
1932 if (engineFPSLimit())
1933 {
1934 gameHandleEvents();
1935 videoClearScreen(0);
1936 gGameMenuMgr.Draw();
1937 videoNextPage();
1938 }
1939 }
1940 if (gGameOptions.nGameType != 0)
1941 {
1942 if (!gDemo.at0 && gDemo.at59ef > 0 && gGameOptions.nGameType == 0 && !bNoDemo)
1943 gDemo.NextDemo();
1944 videoSetViewableArea(0,0,xdim-1,ydim-1);
1945 scrSetDac();
1946 }
1947 goto RESTART;
1948 }
1949 ShutDown();
1950
1951 return 0;
1952 }
1953
S_DefineAudioIfSupported(char * fn,const char * name)1954 static int32_t S_DefineAudioIfSupported(char *fn, const char *name)
1955 {
1956 #if !defined HAVE_FLAC || !defined HAVE_VORBIS
1957 const char *extension = Bstrrchr(name, '.');
1958 # if !defined HAVE_FLAC
1959 if (extension && !Bstrcasecmp(extension, ".flac"))
1960 return -2;
1961 # endif
1962 # if !defined HAVE_VORBIS
1963 if (extension && !Bstrcasecmp(extension, ".ogg"))
1964 return -2;
1965 # endif
1966 #endif
1967 Bstrncpy(fn, name, BMAX_PATH);
1968 return 0;
1969 }
1970
1971 // Returns:
1972 // 0: all OK
1973 // -1: ID declaration was invalid:
S_DefineMusic(const char * ID,const char * name)1974 static int32_t S_DefineMusic(const char *ID, const char *name)
1975 {
1976 int32_t sel = MUS_FIRST_SPECIAL;
1977
1978 Bassert(ID != NULL);
1979
1980 if (!Bstrcmp(ID,"intro"))
1981 {
1982 sel = MUS_INTRO;
1983 }
1984 else if (!Bstrcmp(ID,"loading"))
1985 {
1986 sel = MUS_LOADING;
1987 }
1988 else
1989 {
1990 sel = levelGetMusicIdx(ID);
1991 if (sel < 0)
1992 return -1;
1993 }
1994
1995 int nEpisode = sel/kMaxLevels;
1996 int nLevel = sel%kMaxLevels;
1997 return S_DefineAudioIfSupported(gEpisodeInfo[nEpisode].levelsInfo[nLevel].Song, name);
1998 }
1999
2000 static int parsedefinitions_game(scriptfile *, int);
2001
parsedefinitions_game_include(const char * fileName,scriptfile * pScript,const char * cmdtokptr,int const firstPass)2002 static void parsedefinitions_game_include(const char *fileName, scriptfile *pScript, const char *cmdtokptr, int const firstPass)
2003 {
2004 scriptfile *included = scriptfile_fromfile(fileName);
2005
2006 if (!included)
2007 {
2008 if (!Bstrcasecmp(cmdtokptr,"null") || pScript == NULL) // this is a bit overboard to prevent unused parameter warnings
2009 {
2010 // initprintf("Warning: Failed including %s as module\n", fn);
2011 }
2012 /*
2013 else
2014 {
2015 initprintf("Warning: Failed including %s on line %s:%d\n",
2016 fn, script->filename,scriptfile_getlinum(script,cmdtokptr));
2017 }
2018 */
2019 }
2020 else
2021 {
2022 parsedefinitions_game(included, firstPass);
2023 scriptfile_close(included);
2024 }
2025 }
2026
2027 #if 0
2028 static void parsedefinitions_game_animsounds(scriptfile *pScript, const char * blockEnd, char const * fileName, dukeanim_t * animPtr)
2029 {
2030 Bfree(animPtr->sounds);
2031
2032 size_t numPairs = 0, allocSize = 4;
2033
2034 animPtr->sounds = (animsound_t *)Xmalloc(allocSize * sizeof(animsound_t));
2035 animPtr->numsounds = 0;
2036
2037 int defError = 1;
2038 uint16_t lastFrameNum = 1;
2039
2040 while (pScript->textptr < blockEnd)
2041 {
2042 int32_t frameNum;
2043 int32_t soundNum;
2044
2045 // HACK: we've reached the end of the list
2046 // (hack because it relies on knowledge of
2047 // how scriptfile_* preprocesses the text)
2048 if (blockEnd - pScript->textptr == 1)
2049 break;
2050
2051 // would produce error when it encounters the closing '}'
2052 // without the above hack
2053 if (scriptfile_getnumber(pScript, &frameNum))
2054 break;
2055
2056 defError = 1;
2057
2058 if (scriptfile_getsymbol(pScript, &soundNum))
2059 break;
2060
2061 // frame numbers start at 1 for us
2062 if (frameNum <= 0)
2063 {
2064 initprintf("Error: frame number must be greater zero on line %s:%d\n", pScript->filename,
2065 scriptfile_getlinum(pScript, pScript->ltextptr));
2066 break;
2067 }
2068
2069 if (frameNum < lastFrameNum)
2070 {
2071 initprintf("Error: frame numbers must be in (not necessarily strictly)"
2072 " ascending order (line %s:%d)\n",
2073 pScript->filename, scriptfile_getlinum(pScript, pScript->ltextptr));
2074 break;
2075 }
2076
2077 lastFrameNum = frameNum;
2078
2079 if ((unsigned)soundNum >= MAXSOUNDS && soundNum != -1)
2080 {
2081 initprintf("Error: sound number #%d invalid on line %s:%d\n", soundNum, pScript->filename,
2082 scriptfile_getlinum(pScript, pScript->ltextptr));
2083 break;
2084 }
2085
2086 if (numPairs >= allocSize)
2087 {
2088 allocSize *= 2;
2089 animPtr->sounds = (animsound_t *)Xrealloc(animPtr->sounds, allocSize * sizeof(animsound_t));
2090 }
2091
2092 defError = 0;
2093
2094 animsound_t & sound = animPtr->sounds[numPairs];
2095 sound.frame = frameNum;
2096 sound.sound = soundNum;
2097
2098 ++numPairs;
2099 }
2100
2101 if (!defError)
2102 {
2103 animPtr->numsounds = numPairs;
2104 // initprintf("Defined sound sequence for hi-anim \"%s\" with %d frame/sound pairs\n",
2105 // hardcoded_anim_tokens[animnum].text, numpairs);
2106 }
2107 else
2108 {
2109 DO_FREE_AND_NULL(animPtr->sounds);
2110 initprintf("Failed defining sound sequence for anim \"%s\".\n", fileName);
2111 }
2112 }
2113
2114 #endif
2115
parsedefinitions_game(scriptfile * pScript,int firstPass)2116 static int parsedefinitions_game(scriptfile *pScript, int firstPass)
2117 {
2118 int token;
2119 char *pToken;
2120
2121 static const tokenlist tokens[] =
2122 {
2123 { "include", T_INCLUDE },
2124 { "#include", T_INCLUDE },
2125 { "includedefault", T_INCLUDEDEFAULT },
2126 { "#includedefault", T_INCLUDEDEFAULT },
2127 { "loadgrp", T_LOADGRP },
2128 { "cachesize", T_CACHESIZE },
2129 { "noautoload", T_NOAUTOLOAD },
2130 { "music", T_MUSIC },
2131 { "sound", T_SOUND },
2132 //{ "cutscene", T_CUTSCENE },
2133 //{ "animsounds", T_ANIMSOUNDS },
2134 { "renamefile", T_RENAMEFILE },
2135 { "globalgameflags", T_GLOBALGAMEFLAGS },
2136 { "rffdefineid", T_RFFDEFINEID },
2137 { "tilefromtexture", T_TILEFROMTEXTURE },
2138 };
2139
2140 static const tokenlist soundTokens[] =
2141 {
2142 { "id", T_ID },
2143 { "file", T_FILE },
2144 { "minpitch", T_MINPITCH },
2145 { "maxpitch", T_MAXPITCH },
2146 { "priority", T_PRIORITY },
2147 { "type", T_TYPE },
2148 { "distance", T_DISTANCE },
2149 { "volume", T_VOLUME },
2150 };
2151
2152 #if 0
2153 static const tokenlist animTokens [] =
2154 {
2155 { "delay", T_DELAY },
2156 { "aspect", T_ASPECT },
2157 { "sounds", T_SOUND },
2158 { "forcefilter", T_FORCEFILTER },
2159 { "forcenofilter", T_FORCENOFILTER },
2160 { "texturefilter", T_TEXTUREFILTER },
2161 };
2162 #endif
2163
2164 do
2165 {
2166 token = getatoken(pScript, tokens, ARRAY_SIZE(tokens));
2167 pToken = pScript->ltextptr;
2168
2169 switch (token)
2170 {
2171 case T_LOADGRP:
2172 {
2173 char *fileName;
2174
2175 pathsearchmode = 1;
2176 if (!scriptfile_getstring(pScript,&fileName) && firstPass)
2177 {
2178 if (initgroupfile(fileName) == -1)
2179 initprintf("Could not find file \"%s\".\n", fileName);
2180 else
2181 {
2182 initprintf("Using file \"%s\" as game data.\n", fileName);
2183 if (!bNoAutoLoad && !gSetup.noautoload)
2184 G_DoAutoload(fileName);
2185 }
2186 }
2187
2188 pathsearchmode = 0;
2189 }
2190 break;
2191 case T_CACHESIZE:
2192 {
2193 int32_t cacheSize;
2194
2195 if (scriptfile_getnumber(pScript, &cacheSize) || !firstPass)
2196 break;
2197
2198 if (cacheSize > 0)
2199 MAXCACHE1DSIZE = cacheSize << 10;
2200 }
2201 break;
2202 case T_INCLUDE:
2203 {
2204 char *fileName;
2205
2206 if (!scriptfile_getstring(pScript, &fileName))
2207 parsedefinitions_game_include(fileName, pScript, pToken, firstPass);
2208
2209 break;
2210 }
2211 case T_INCLUDEDEFAULT:
2212 {
2213 parsedefinitions_game_include(G_DefaultDefFile(), pScript, pToken, firstPass);
2214 break;
2215 }
2216 case T_NOAUTOLOAD:
2217 if (firstPass)
2218 bNoAutoLoad = true;
2219 break;
2220 case T_MUSIC:
2221 {
2222 char *tokenPtr = pScript->ltextptr;
2223 char *musicID = NULL;
2224 char *fileName = NULL;
2225 char *musicEnd;
2226
2227 if (scriptfile_getbraces(pScript, &musicEnd))
2228 break;
2229
2230 while (pScript->textptr < musicEnd)
2231 {
2232 switch (getatoken(pScript, soundTokens, ARRAY_SIZE(soundTokens)))
2233 {
2234 case T_ID: scriptfile_getstring(pScript, &musicID); break;
2235 case T_FILE: scriptfile_getstring(pScript, &fileName); break;
2236 }
2237 }
2238
2239 if (!firstPass)
2240 {
2241 if (musicID==NULL)
2242 {
2243 initprintf("Error: missing ID for music definition near line %s:%d\n",
2244 pScript->filename, scriptfile_getlinum(pScript,tokenPtr));
2245 break;
2246 }
2247
2248 if (fileName == NULL || check_file_exist(fileName))
2249 break;
2250
2251 if (S_DefineMusic(musicID, fileName) == -1)
2252 initprintf("Error: invalid music ID on line %s:%d\n", pScript->filename, scriptfile_getlinum(pScript, tokenPtr));
2253 }
2254 }
2255 break;
2256
2257 case T_RFFDEFINEID:
2258 {
2259 char *resName = NULL;
2260 char *resType = NULL;
2261 char *rffName = NULL;
2262 int resID;
2263
2264 if (scriptfile_getstring(pScript, &resName))
2265 break;
2266
2267 if (scriptfile_getstring(pScript, &resType))
2268 break;
2269
2270 if (scriptfile_getnumber(pScript, &resID))
2271 break;
2272
2273 if (scriptfile_getstring(pScript, &rffName))
2274 break;
2275
2276 if (!firstPass)
2277 {
2278 if (!Bstrcasecmp(rffName, "SYSTEM"))
2279 gSysRes.AddExternalResource(resName, resType, resID);
2280 else if (!Bstrcasecmp(rffName, "SOUND"))
2281 gSoundRes.AddExternalResource(resName, resType, resID);
2282 }
2283 }
2284 break;
2285
2286 case T_TILEFROMTEXTURE:
2287 {
2288 char *texturetokptr = pScript->ltextptr, *textureend;
2289 int32_t tile = -1;
2290 int32_t havesurface = 0, havevox = 0, haveview = 0, haveshade = 0;
2291 int32_t surface = 0, vox = 0, view = 0, shade = 0;
2292 int32_t tile_crc32 = 0;
2293 vec2_t tile_size{};
2294 uint8_t have_crc32 = 0;
2295 uint8_t have_size = 0;
2296
2297 static const tokenlist tilefromtexturetokens[] =
2298 {
2299 { "surface", T_SURFACE },
2300 { "voxel", T_VOXEL },
2301 { "ifcrc", T_IFCRC },
2302 { "view", T_VIEW },
2303 { "shade", T_SHADE },
2304 };
2305
2306 if (scriptfile_getsymbol(pScript,&tile)) break;
2307 if (scriptfile_getbraces(pScript,&textureend)) break;
2308 while (pScript->textptr < textureend)
2309 {
2310 int32_t token = getatoken(pScript,tilefromtexturetokens,ARRAY_SIZE(tilefromtexturetokens));
2311 switch (token)
2312 {
2313 case T_IFCRC:
2314 scriptfile_getsymbol(pScript, &tile_crc32);
2315 have_crc32 = 1;
2316 break;
2317 case T_IFMATCH:
2318 {
2319 char *ifmatchend;
2320
2321 static const tokenlist ifmatchtokens[] =
2322 {
2323 { "crc32", T_CRC32 },
2324 { "size", T_SIZE },
2325 };
2326
2327 if (scriptfile_getbraces(pScript,&ifmatchend)) break;
2328 while (pScript->textptr < ifmatchend)
2329 {
2330 int32_t token = getatoken(pScript,ifmatchtokens,ARRAY_SIZE(ifmatchtokens));
2331 switch (token)
2332 {
2333 case T_CRC32:
2334 scriptfile_getsymbol(pScript, &tile_crc32);
2335 have_crc32 = 1;
2336 break;
2337 case T_SIZE:
2338 scriptfile_getsymbol(pScript, &tile_size.x);
2339 scriptfile_getsymbol(pScript, &tile_size.y);
2340 have_size = 1;
2341 break;
2342 default:
2343 break;
2344 }
2345 }
2346 break;
2347 }
2348 case T_SURFACE:
2349 havesurface = 1;
2350 scriptfile_getsymbol(pScript, &surface);
2351 break;
2352 case T_VOXEL:
2353 havevox = 1;
2354 scriptfile_getsymbol(pScript, &vox);
2355 break;
2356 case T_VIEW:
2357 haveview = 1;
2358 scriptfile_getsymbol(pScript, &view);
2359 break;
2360 case T_SHADE:
2361 haveshade = 1;
2362 scriptfile_getsymbol(pScript, &shade);
2363 break;
2364 }
2365 }
2366
2367 if (!firstPass)
2368 {
2369 if (EDUKE32_PREDICT_FALSE((unsigned)tile >= MAXUSERTILES))
2370 {
2371 initprintf("Error: missing or invalid 'tile number' for texture definition near line %s:%d\n",
2372 pScript->filename, scriptfile_getlinum(pScript,texturetokptr));
2373 break;
2374 }
2375
2376 if (have_crc32)
2377 {
2378 int32_t const orig_crc32 = tileGetCRC32(tile);
2379 if (orig_crc32 != tile_crc32)
2380 {
2381 // initprintf("CRC32 of tile %d doesn't match! CRC32: %d, Expected: %d\n", tile, orig_crc32, tile_crc32);
2382 break;
2383 }
2384 }
2385
2386 if (have_size)
2387 {
2388 vec2_16_t const orig_size = tileGetSize(tile);
2389 if (orig_size.x != tile_size.x && orig_size.y != tile_size.y)
2390 {
2391 // initprintf("Size of tile %d doesn't match! Size: (%d, %d), Expected: (%d, %d)\n", tile, orig_size.x, orig_size.y, tile_size.x, tile_size.y);
2392 break;
2393 }
2394 }
2395
2396 if (havesurface)
2397 surfType[tile] = surface;
2398 if (havevox)
2399 voxelIndex[tile] = vox;
2400 if (haveshade)
2401 tileShade[tile] = shade;
2402 if (haveview)
2403 picanm[tile].extra = view&7;
2404 }
2405 }
2406 break;
2407
2408 #if 0
2409 case T_CUTSCENE:
2410 {
2411 char *fileName = NULL;
2412
2413 scriptfile_getstring(pScript, &fileName);
2414
2415 char *animEnd;
2416
2417 if (scriptfile_getbraces(pScript, &animEnd))
2418 break;
2419
2420 if (!firstPass)
2421 {
2422 dukeanim_t *animPtr = Anim_Find(fileName);
2423
2424 if (!animPtr)
2425 {
2426 animPtr = Anim_Create(fileName);
2427 animPtr->framedelay = 10;
2428 animPtr->frameflags = 0;
2429 }
2430
2431 int32_t temp;
2432
2433 while (pScript->textptr < animEnd)
2434 {
2435 switch (getatoken(pScript, animTokens, ARRAY_SIZE(animTokens)))
2436 {
2437 case T_DELAY:
2438 scriptfile_getnumber(pScript, &temp);
2439 animPtr->framedelay = temp;
2440 break;
2441 case T_ASPECT:
2442 {
2443 double dtemp, dtemp2;
2444 scriptfile_getdouble(pScript, &dtemp);
2445 scriptfile_getdouble(pScript, &dtemp2);
2446 animPtr->frameaspect1 = dtemp;
2447 animPtr->frameaspect2 = dtemp2;
2448 break;
2449 }
2450 case T_SOUND:
2451 {
2452 char *animSoundsEnd = NULL;
2453 if (scriptfile_getbraces(pScript, &animSoundsEnd))
2454 break;
2455 parsedefinitions_game_animsounds(pScript, animSoundsEnd, fileName, animPtr);
2456 break;
2457 }
2458 case T_FORCEFILTER:
2459 animPtr->frameflags |= CUTSCENE_FORCEFILTER;
2460 break;
2461 case T_FORCENOFILTER:
2462 animPtr->frameflags |= CUTSCENE_FORCENOFILTER;
2463 break;
2464 case T_TEXTUREFILTER:
2465 animPtr->frameflags |= CUTSCENE_TEXTUREFILTER;
2466 break;
2467 }
2468 }
2469 }
2470 else
2471 pScript->textptr = animEnd;
2472 }
2473 break;
2474 case T_ANIMSOUNDS:
2475 {
2476 char *tokenPtr = pScript->ltextptr;
2477 char *fileName = NULL;
2478
2479 scriptfile_getstring(pScript, &fileName);
2480 if (!fileName)
2481 break;
2482
2483 char *animSoundsEnd = NULL;
2484
2485 if (scriptfile_getbraces(pScript, &animSoundsEnd))
2486 break;
2487
2488 if (firstPass)
2489 {
2490 pScript->textptr = animSoundsEnd;
2491 break;
2492 }
2493
2494 dukeanim_t *animPtr = Anim_Find(fileName);
2495
2496 if (!animPtr)
2497 {
2498 initprintf("Error: expected animation filename on line %s:%d\n",
2499 pScript->filename, scriptfile_getlinum(pScript, tokenPtr));
2500 break;
2501 }
2502
2503 parsedefinitions_game_animsounds(pScript, animSoundsEnd, fileName, animPtr);
2504 }
2505 break;
2506 case T_SOUND:
2507 {
2508 char *tokenPtr = pScript->ltextptr;
2509 char *fileName = NULL;
2510 char *musicEnd;
2511
2512 double volume = 1.0;
2513
2514 int32_t soundNum = -1;
2515 int32_t maxpitch = 0;
2516 int32_t minpitch = 0;
2517 int32_t priority = 0;
2518 int32_t type = 0;
2519 int32_t distance = 0;
2520
2521 if (scriptfile_getbraces(pScript, &musicEnd))
2522 break;
2523
2524 while (pScript->textptr < musicEnd)
2525 {
2526 switch (getatoken(pScript, soundTokens, ARRAY_SIZE(soundTokens)))
2527 {
2528 case T_ID: scriptfile_getsymbol(pScript, &soundNum); break;
2529 case T_FILE: scriptfile_getstring(pScript, &fileName); break;
2530 case T_MINPITCH: scriptfile_getsymbol(pScript, &minpitch); break;
2531 case T_MAXPITCH: scriptfile_getsymbol(pScript, &maxpitch); break;
2532 case T_PRIORITY: scriptfile_getsymbol(pScript, &priority); break;
2533 case T_TYPE: scriptfile_getsymbol(pScript, &type); break;
2534 case T_DISTANCE: scriptfile_getsymbol(pScript, &distance); break;
2535 case T_VOLUME: scriptfile_getdouble(pScript, &volume); break;
2536 }
2537 }
2538
2539 if (!firstPass)
2540 {
2541 if (soundNum==-1)
2542 {
2543 initprintf("Error: missing ID for sound definition near line %s:%d\n", pScript->filename, scriptfile_getlinum(pScript,tokenPtr));
2544 break;
2545 }
2546
2547 if (fileName == NULL || check_file_exist(fileName))
2548 break;
2549
2550 // maybe I should have just packed this into a sound_t and passed a reference...
2551 if (S_DefineSound(soundNum, fileName, minpitch, maxpitch, priority, type, distance, volume) == -1)
2552 initprintf("Error: invalid sound ID on line %s:%d\n", pScript->filename, scriptfile_getlinum(pScript,tokenPtr));
2553 }
2554 }
2555 break;
2556 #endif
2557 case T_GLOBALGAMEFLAGS: scriptfile_getnumber(pScript, &blood_globalflags); break;
2558 case T_EOF: return 0;
2559 default: break;
2560 }
2561 }
2562 while (1);
2563
2564 return 0;
2565 }
2566
loaddefinitions_game(const char * fileName,int32_t firstPass)2567 int loaddefinitions_game(const char *fileName, int32_t firstPass)
2568 {
2569 scriptfile *pScript = scriptfile_fromfile(fileName);
2570
2571 if (pScript)
2572 parsedefinitions_game(pScript, firstPass);
2573
2574 for (char const * m : g_defModules)
2575 parsedefinitions_game_include(m, NULL, "null", firstPass);
2576
2577 if (pScript)
2578 scriptfile_close(pScript);
2579
2580 scriptfile_clearsymbols();
2581
2582 return 0;
2583 }
2584
2585 INICHAIN *pINIChain;
2586 INICHAIN const*pINISelected;
2587 int nINICount = 0;
2588
2589 const char *pzCrypticArts[] = {
2590 "CPART07.AR_", "CPART15.AR_"
2591 };
2592
2593 INIDESCRIPTION gINIDescription[] = {
2594 { "BLOOD: One Unit Whole Blood", "BLOOD.INI", NULL, 0 },
2595 { "Cryptic passage", "CRYPTIC.INI", pzCrypticArts, ARRAY_SSIZE(pzCrypticArts) },
2596 };
2597
AddINIFile(const char * pzFile,bool bForce=false)2598 bool AddINIFile(const char *pzFile, bool bForce = false)
2599 {
2600 char *pzFN;
2601 struct Bstat st;
2602 static INICHAIN *pINIIter = NULL;
2603 if (!bForce)
2604 {
2605 if (findfrompath(pzFile, &pzFN)) return false; // failed to resolve the filename
2606 if (Bstat(pzFN, &st))
2607 {
2608 Bfree(pzFN);
2609 return false;
2610 } // failed to stat the file
2611 Bfree(pzFN);
2612 IniFile *pTempIni = new IniFile(pzFile);
2613 if (!pTempIni->FindSection("Episode1"))
2614 {
2615 delete pTempIni;
2616 return false;
2617 }
2618 delete pTempIni;
2619 }
2620 if (!pINIChain)
2621 pINIIter = pINIChain = new INICHAIN;
2622 else
2623 pINIIter = pINIIter->pNext = new INICHAIN;
2624 pINIIter->pNext = NULL;
2625 pINIIter->pDescription = NULL;
2626 Bstrncpy(pINIIter->zName, pzFile, BMAX_PATH);
2627 for (int i = 0; i < ARRAY_SSIZE(gINIDescription); i++)
2628 {
2629 if (!Bstrncasecmp(pINIIter->zName, gINIDescription[i].pzFilename, BMAX_PATH))
2630 {
2631 pINIIter->pDescription = &gINIDescription[i];
2632 break;
2633 }
2634 }
2635 return true;
2636 }
2637
ScanINIFiles(void)2638 void ScanINIFiles(void)
2639 {
2640 nINICount = 0;
2641 BUILDVFS_FIND_REC *pINIList = klistpath("/", "*.ini", BUILDVFS_FIND_FILE);
2642 pINIChain = NULL;
2643 bool bINIExists = false;
2644 for (auto pIter = pINIList; pIter; pIter = pIter->next)
2645 {
2646 if (!Bstrncasecmp(BloodIniFile, pIter->name, BMAX_PATH))
2647 {
2648 bINIExists = true;
2649 }
2650 AddINIFile(pIter->name);
2651 }
2652
2653 if ((bINIOverride && !bINIExists) || !pINIList)
2654 {
2655 AddINIFile(BloodIniFile, true);
2656 }
2657 klistfree(pINIList);
2658 pINISelected = pINIChain;
2659 for (auto pIter = pINIChain; pIter; pIter = pIter->pNext)
2660 {
2661 if (!Bstrncasecmp(BloodIniFile, pIter->zName, BMAX_PATH))
2662 {
2663 pINISelected = pIter;
2664 break;
2665 }
2666 }
2667 }
2668
LoadArtFile(const char * pzFile)2669 bool LoadArtFile(const char *pzFile)
2670 {
2671 int hFile = kopen4loadfrommod(pzFile, 0);
2672 if (hFile == -1)
2673 {
2674 initprintf("Can't open extra art file:\"%s\"\n", pzFile);
2675 return false;
2676 }
2677 artheader_t artheader;
2678 int nStatus = artReadHeader(hFile, pzFile, &artheader);
2679 if (nStatus != 0)
2680 {
2681 kclose(hFile);
2682 initprintf("Error reading extra art file:\"%s\"\n", pzFile);
2683 return false;
2684 }
2685 for (int i = artheader.tilestart; i <= artheader.tileend; i++)
2686 tileDelete(i);
2687 artReadManifest(hFile, &artheader);
2688 artPreloadFile(hFile, &artheader);
2689 for (int i = artheader.tilestart; i <= artheader.tileend; i++)
2690 tileUpdatePicSiz(i);
2691 kclose(hFile);
2692 return true;
2693 }
2694
LoadExtraArts(void)2695 void LoadExtraArts(void)
2696 {
2697 if (!pINISelected->pDescription)
2698 return;
2699 for (int i = 0; i < pINISelected->pDescription->nArts; i++)
2700 {
2701 LoadArtFile(pINISelected->pDescription->pzArts[i]);
2702 }
2703 }
2704
DemoRecordStatus(void)2705 bool DemoRecordStatus(void) {
2706 return gDemo.at0;
2707 }
2708
VanillaMode()2709 bool VanillaMode() {
2710 return gDemo.m_bLegacy && gDemo.at1;
2711 }
2712
fileExistsRFF(int id,const char * ext)2713 bool fileExistsRFF(int id, const char *ext) {
2714 return gSysRes.Lookup(id, ext);
2715 }
2716
sndTryPlaySpecialMusic(int nMusic)2717 int sndTryPlaySpecialMusic(int nMusic)
2718 {
2719 int nEpisode = nMusic/kMaxLevels;
2720 int nLevel = nMusic%kMaxLevels;
2721 if (!sndPlaySong(gEpisodeInfo[nEpisode].levelsInfo[nLevel].Song, true))
2722 {
2723 strncpy(gGameOptions.zLevelSong, gEpisodeInfo[nEpisode].levelsInfo[nLevel].Song, BMAX_PATH);
2724 return 0;
2725 }
2726 return 1;
2727 }
2728
sndPlaySpecialMusicOrNothing(int nMusic)2729 void sndPlaySpecialMusicOrNothing(int nMusic)
2730 {
2731 int nEpisode = nMusic/kMaxLevels;
2732 int nLevel = nMusic%kMaxLevels;
2733 if (sndTryPlaySpecialMusic(nMusic))
2734 {
2735 sndStopSong();
2736 strncpy(gGameOptions.zLevelSong, gEpisodeInfo[nEpisode].levelsInfo[nLevel].Song, BMAX_PATH);
2737 }
2738 }
2739