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