1 //
2 // Common non-engine code/data for EDuke32 and Mapster32
3 //
4
5 #include "compat.h"
6 #include "build.h"
7 #include "baselayer.h"
8 #include "palette.h"
9
10 #include "grpscan.h"
11
12 #include "vfs.h"
13
14 #ifdef _WIN32
15 # include "windows_inc.h"
16 # include "winbits.h"
17 #elif defined __APPLE__
18 # include "osxbits.h"
19 #endif
20
21 #include "common.h"
22 #include "common_game.h"
23
24 struct grpfile_t const* g_selectedGrp;
25
26 int32_t g_gameType = GAMEFLAG_TEKWAR;
27 int g_addonNum = 0;
28
29 int r_showfps;
30
31 // g_gameNamePtr can point to one of: grpfiles[].name (string literal), string
32 // literal, malloc'd block (XXX: possible leak)
33 const char* g_gameNamePtr = NULL;
34
35 // grp/con handling
36
37 static const char* defaultgamegrp = "tekwar.pk3";
38 static const char* defaultdeffilename = "tekwar.def";
39
40 // g_grpNamePtr can ONLY point to a malloc'd block (length BMAX_PATH)
41 char* g_grpNamePtr = NULL;
42 // g_scriptNamePtr can ONLY point to a malloc'd block (length BMAX_PATH)
43 char* g_scriptNamePtr = NULL;
44 // g_rtsNamePtr can ONLY point to a malloc'd block (length BMAX_PATH)
45 char* g_rtsNamePtr = NULL;
46
clearGrpNamePtr(void)47 void clearGrpNamePtr(void)
48 {
49 Xfree(g_grpNamePtr);
50 // g_grpNamePtr assumed to be assigned to right after
51 }
52
clearScriptNamePtr(void)53 void clearScriptNamePtr(void)
54 {
55 Xfree(g_scriptNamePtr);
56 // g_scriptNamePtr assumed to be assigned to right after
57 }
58
G_DefaultGrpFile(void)59 const char* G_DefaultGrpFile(void)
60 {
61 return defaultgamegrp;
62 }
G_DefaultDefFile(void)63 const char* G_DefaultDefFile(void)
64 {
65 return defaultdeffilename;
66 }
67
G_GrpFile(void)68 const char* G_GrpFile(void)
69 {
70 return (g_grpNamePtr == NULL) ? G_DefaultGrpFile() : g_grpNamePtr;
71 }
72
G_DefFile(void)73 const char* G_DefFile(void)
74 {
75 return (g_defNamePtr == NULL) ? G_DefaultDefFile() : g_defNamePtr;
76 }
77
78 //////////
79
80 // Set up new-style multi-psky handling.
G_InitMultiPsky(int CLOUDYOCEAN__DYN,int MOONSKY1__DYN,int BIGORBIT1__DYN,int LA__DYN)81 void G_InitMultiPsky(int CLOUDYOCEAN__DYN, int MOONSKY1__DYN, int BIGORBIT1__DYN, int LA__DYN)
82 {
83 // When adding other multi-skies, take care that the tileofs[] values are
84 // <= PSKYOFF_MAX. (It can be increased up to MAXPSKYTILES, but should be
85 // set as tight as possible.)
86
87 // The default sky properties (all others are implicitly zero):
88 psky_t* sky = tileSetupSky(DEFAULTPSKY);
89 sky->lognumtiles = 3;
90 sky->horizfrac = 32768;
91
92 // CLOUDYOCEAN
93 // Aligns with the drawn scene horizon because it has one itself.
94 sky = tileSetupSky(CLOUDYOCEAN__DYN);
95 sky->lognumtiles = 3;
96 sky->horizfrac = 65536;
97
98 // MOONSKY1
99 // earth mountain mountain sun
100 sky = tileSetupSky(MOONSKY1__DYN);
101 sky->lognumtiles = 3;
102 sky->horizfrac = 32768;
103 sky->tileofs[6] = 1;
104 sky->tileofs[1] = 2;
105 sky->tileofs[4] = 2;
106 sky->tileofs[2] = 3;
107
108 // BIGORBIT1 // orbit
109 // earth1 2 3 moon/sun
110 sky = tileSetupSky(BIGORBIT1__DYN);
111 sky->lognumtiles = 3;
112 sky->horizfrac = 32768;
113 sky->tileofs[5] = 1;
114 sky->tileofs[6] = 2;
115 sky->tileofs[7] = 3;
116 sky->tileofs[2] = 4;
117
118 // LA // la city
119 // earth1 2 3 moon/sun
120 sky = tileSetupSky(LA__DYN);
121 sky->lognumtiles = 3;
122 sky->horizfrac = 16384 + 1024;
123 sky->tileofs[0] = 1;
124 sky->tileofs[1] = 2;
125 sky->tileofs[2] = 1;
126 sky->tileofs[3] = 3;
127 sky->tileofs[4] = 4;
128 sky->tileofs[5] = 0;
129 sky->tileofs[6] = 2;
130 sky->tileofs[7] = 3;
131
132 #if 0
133 // This assertion should hold. See note above.
134 for (bssize_t i = 0; i < pskynummultis; ++i)
135 for (bssize_t j = 0; j < (1 << multipsky[i].lognumtiles); ++j)
136 Bassert(multipsky[i].tileofs[j] <= PSKYOFF_MAX);
137 #endif
138 }
139
G_SetupGlobalPsky(void)140 void G_SetupGlobalPsky(void)
141 {
142 int skyIdx = 0;
143
144 // NOTE: Loop must be running backwards for the same behavior as the game
145 // (greatest sector index with matching parallaxed sky takes precedence).
146 for (int i = numsectors - 1; i >= 0; i--)
147 {
148 if (sector[i].ceilingstat & 1)
149 {
150 skyIdx = getpskyidx(sector[i].ceilingpicnum);
151 if (skyIdx > 0)
152 break;
153 }
154 }
155
156 g_pskyidx = skyIdx;
157 }
158
159 //////////
160
161 static char g_rootDir[BMAX_PATH];
162
163 int g_useCwd;
164 int32_t g_groupFileHandle;
165
166 static struct strllist* CommandPaths, * CommandGrps;
167
G_ExtPreInit(int32_t argc,char const * const * argv)168 void G_ExtPreInit(int32_t argc, char const* const* argv)
169 {
170 g_useCwd = G_CheckCmdSwitch(argc, argv, "-usecwd");
171
172 #ifdef _WIN32
173 GetModuleFileName(NULL, g_rootDir, BMAX_PATH);
174 Bcorrectfilename(g_rootDir, 1);
175 //buildvfs_chdir(g_rootDir);
176 #else
177 buildvfs_getcwd(g_rootDir, BMAX_PATH);
178 strcat(g_rootDir, "/");
179 #endif
180 }
181
G_ExtInit(void)182 void G_ExtInit(void)
183 {
184 char cwd[BMAX_PATH];
185
186 #ifdef EDUKE32_OSX
187 char* appdir = Bgetappdir();
188 addsearchpath(appdir);
189 Xfree(appdir);
190 #endif
191
192 #ifdef USE_PHYSFS
193 strncpy(cwd, PHYSFS_getBaseDir(), ARRAY_SIZE(cwd));
194 cwd[ARRAY_SIZE(cwd) - 1] = '\0';
195 #else
196 if (buildvfs_getcwd(cwd, ARRAY_SIZE(cwd)) && Bstrcmp(cwd, "/") != 0)
197 #endif
198 addsearchpath(cwd);
199
200 // TODO:
201 if (CommandPaths)
202 {
203 int32_t i;
204 struct strllist* s;
205 while (CommandPaths)
206 {
207 s = CommandPaths->next;
208 i = addsearchpath(CommandPaths->str);
209 if (i < 0)
210 {
211 initprintf("Failed adding %s for game data: %s\n", CommandPaths->str,
212 i == -1 ? "not a directory" : "no such directory");
213 }
214
215 Xfree(CommandPaths->str);
216 Xfree(CommandPaths);
217 CommandPaths = s;
218 }
219 }
220
221 #if defined(_WIN32) && !defined(EDUKE32_STANDALONE)
222 if (buildvfs_exists("user_profiles_enabled"))
223 #else
224 if (g_useCwd == 0 && !buildvfs_exists("user_profiles_disabled"))
225 #endif
226 {
227 char* homedir;
228 int32_t asperr;
229
230 if ((homedir = Bgethomedir()))
231 {
232 Bsnprintf(cwd, ARRAY_SIZE(cwd), "%s/"
233 #if defined(_WIN32)
234 APPNAME
235 #elif defined(GEKKO)
236 "apps/" APPBASENAME
237 #else
238 ".config/" APPBASENAME
239 #endif
240 , homedir);
241 asperr = addsearchpath(cwd);
242 if (asperr == -2)
243 {
244 if (buildvfs_mkdir(cwd, S_IRWXU) == 0) asperr = addsearchpath(cwd);
245 else asperr = -1;
246 }
247 if (asperr == 0)
248 buildvfs_chdir(cwd);
249 Xfree(homedir);
250 }
251 }
252 }
253
G_ScanGroups(void)254 void G_ScanGroups(void)
255 {
256 ScanGroups();
257
258 g_selectedGrp = NULL;
259
260 char const* const currentGrp = G_GrpFile();
261
262 for (grpfile_t const* fg = foundgrps; fg; fg = fg->next)
263 {
264 if (!Bstrcasecmp(fg->filename, currentGrp))
265 {
266 g_selectedGrp = fg;
267 break;
268 }
269 }
270
271 if (g_selectedGrp == NULL)
272 g_selectedGrp = foundgrps;
273 }
274
G_TryLoadingGrp(char const * const grpfile)275 static int32_t G_TryLoadingGrp(char const* const grpfile)
276 {
277 int32_t i;
278
279 if ((i = initgroupfile(grpfile)) == -1)
280 initprintf("Warning: could not find main data file \"%s\"!\n", grpfile);
281 else
282 initprintf("Using \"%s\" as main game data file.\n", grpfile);
283
284 return i;
285 }
286
G_LoadGrpDependencyChain(grpfile_t const * const grp)287 static int32_t G_LoadGrpDependencyChain(grpfile_t const* const grp)
288 {
289 if (!grp)
290 return -1;
291
292 if (grp->type->dependency && grp->type->dependency != grp->type->crcval)
293 G_LoadGrpDependencyChain(FindGroup(grp->type->dependency));
294
295 int32_t const i = G_TryLoadingGrp(grp->filename);
296
297 return i;
298 }
299
G_LoadGroups(int32_t autoload)300 void G_LoadGroups(int32_t autoload)
301 {
302 if (g_modDir[0] != '/')
303 {
304 char cwd[BMAX_PATH];
305
306 Bstrcat(g_rootDir, g_modDir);
307 addsearchpath(g_rootDir);
308 // addsearchpath(mod_dir);
309
310 char path[BMAX_PATH];
311
312 if (buildvfs_getcwd(cwd, BMAX_PATH))
313 {
314 Bsnprintf(path, sizeof(path), "%s/%s", cwd, g_modDir);
315 if (!Bstrcmp(g_rootDir, path))
316 {
317 if (addsearchpath(path) == -2)
318 if (buildvfs_mkdir(path, S_IRWXU) == 0)
319 addsearchpath(path);
320 }
321 }
322
323 #ifdef USE_OPENGL
324 Bsnprintf(path, sizeof(path), "%s/%s", g_modDir, TEXCACHEFILE);
325 Bstrcpy(TEXCACHEFILE, path);
326 #endif
327 }
328
329 const char* grpfile;
330 int32_t i;
331
332 if ((i = G_LoadGrpDependencyChain(g_selectedGrp)) != -1)
333 {
334 grpfile = g_selectedGrp->filename;
335
336 clearGrpNamePtr();
337 g_grpNamePtr = dup_filename(grpfile);
338
339 grpinfo_t const* const type = g_selectedGrp->type;
340
341 g_gameType = type->game;
342 g_gameNamePtr = type->name;
343
344 if (type->defname && g_defNamePtr == NULL)
345 g_defNamePtr = dup_filename(type->defname);
346 }
347 else
348 {
349 grpfile = G_GrpFile();
350 i = G_TryLoadingGrp(grpfile);
351 }
352
353 if (autoload)
354 {
355 G_LoadGroupsInDir("autoload");
356
357 if (i != -1)
358 G_DoAutoload(grpfile);
359 }
360
361 if (g_modDir[0] != '/')
362 G_LoadGroupsInDir(g_modDir);
363
364 loaddefinitions_game(G_DefFile(), TRUE);
365
366 struct strllist* s;
367
368 int const bakpathsearchmode = pathsearchmode;
369 pathsearchmode = 1;
370
371 while (CommandGrps)
372 {
373 int32_t j;
374
375 s = CommandGrps->next;
376
377 if ((j = initgroupfile(CommandGrps->str)) == -1)
378 initprintf("Could not find file \"%s\".\n", CommandGrps->str);
379 else
380 {
381 g_groupFileHandle = j;
382 initprintf("Using file \"%s\" as game data.\n", CommandGrps->str);
383 if (autoload)
384 G_DoAutoload(CommandGrps->str);
385 }
386
387 Xfree(CommandGrps->str);
388 Xfree(CommandGrps);
389 CommandGrps = s;
390 }
391 pathsearchmode = bakpathsearchmode;
392 }
393
G_CleanupSearchPaths(void)394 void G_CleanupSearchPaths(void)
395 {
396 removesearchpaths_withuser(SEARCHPATH_REMOVE);
397 }
398
399 //////////
400
401 GrowArray<char*> g_scriptModules;
402
G_AddGroup(const char * buffer)403 void G_AddGroup(const char* buffer)
404 {
405 char buf[BMAX_PATH];
406
407 struct strllist* s = (struct strllist*)Xcalloc(1, sizeof(struct strllist));
408
409 Bstrcpy(buf, buffer);
410
411 if (Bstrchr(buf, '.') == 0)
412 Bstrcat(buf, ".grp");
413
414 s->str = Xstrdup(buf);
415
416 if (CommandGrps)
417 {
418 struct strllist* t;
419 for (t = CommandGrps; t->next; t = t->next);
420 t->next = s;
421 return;
422 }
423 CommandGrps = s;
424 }
425
G_AddPath(const char * buffer)426 void G_AddPath(const char* buffer)
427 {
428 struct strllist* s = (struct strllist*)Xcalloc(1, sizeof(struct strllist));
429 s->str = Xstrdup(buffer);
430
431 if (CommandPaths)
432 {
433 struct strllist* t;
434 for (t = CommandPaths; t->next; t = t->next);
435 t->next = s;
436 return;
437 }
438 CommandPaths = s;
439 }
440
G_AddCon(const char * buffer)441 void G_AddCon(const char* buffer)
442 {
443 clearScriptNamePtr();
444 g_scriptNamePtr = dup_filename(buffer);
445 initprintf("Using CON file \"%s\".\n", g_scriptNamePtr);
446 }
447
G_AddConModule(const char * buffer)448 void G_AddConModule(const char* buffer)
449 {
450 g_scriptModules.append(Xstrdup(buffer));
451 }
452
453 //////////
454
455 // loads all group (grp, zip, pk3/4) files in the given directory
G_LoadGroupsInDir(const char * dirname)456 void G_LoadGroupsInDir(const char* dirname)
457 {
458 static const char* extensions[] = { "*.grp", "*.zip", "*.ssi", "*.pk3", "*.pk4" };
459 char buf[BMAX_PATH];
460 fnlist_t fnlist = FNLIST_INITIALIZER;
461
462 for (auto& extension : extensions)
463 {
464 BUILDVFS_FIND_REC* rec;
465
466 fnlist_getnames(&fnlist, dirname, extension, -1, 0);
467
468 for (rec = fnlist.findfiles; rec; rec = rec->next)
469 {
470 Bsnprintf(buf, sizeof(buf), "%s/%s", dirname, rec->name);
471 initprintf("Using group file \"%s\".\n", buf);
472 initgroupfile(buf);
473 }
474
475 fnlist_clearnames(&fnlist);
476 }
477 }
478
G_DoAutoload(const char * dirname)479 void G_DoAutoload(const char* dirname)
480 {
481 char buf[BMAX_PATH];
482
483 Bsnprintf(buf, sizeof(buf), "autoload/%s", dirname);
484 G_LoadGroupsInDir(buf);
485 }
486
487 //////////
488
G_LoadLookups(void)489 void G_LoadLookups(void)
490 {
491 int32_t j;
492 buildvfs_kfd fp;
493
494 if ((fp = kopen4loadfrommod("lookup.dat", 0)) == buildvfs_kfd_invalid)
495 if ((fp = kopen4loadfrommod("lookup.dat", 1)) == buildvfs_kfd_invalid)
496 return;
497
498 j = paletteLoadLookupTable(fp);
499
500 if (j < 0)
501 {
502 if (j == -1)
503 initprintf("ERROR loading \"lookup.dat\": failed reading enough data.\n");
504
505 return kclose(fp);
506 }
507
508 uint8_t paldata[768];
509
510 for (j = 1; j <= 5; j++)
511 {
512 // Account for TITLE and REALMS swap between basepal number and on-disk order.
513 int32_t basepalnum = (j == 3 || j == 4) ? 4 + 3 - j : j;
514
515 if (kread_and_test(fp, paldata, 768))
516 return kclose(fp);
517
518 for (unsigned char& k : paldata)
519 k <<= 2;
520
521 paletteSetColorTable(basepalnum, paldata);
522 }
523
524 kclose(fp);
525 }
526
527 //////////
528
529 #ifdef FORMAT_UPGRADE_ELIGIBLE
530 int g_maybeUpgradeSoundFormats = 1;
531
S_TryFormats(char * const testfn,char * const fn_suffix,char const searchfirst)532 static buildvfs_kfd S_TryFormats(char* const testfn, char* const fn_suffix, char const searchfirst)
533 {
534 if (!g_maybeUpgradeSoundFormats)
535 return buildvfs_kfd_invalid;
536
537 static char const* extensions[] =
538 {
539 #ifdef HAVE_FLAC
540 ".flac",
541 #endif
542 #ifdef HAVE_VORBIS
543 ".ogg",
544 #endif
545 };
546
547 for (char const* ext : extensions)
548 {
549 Bstrcpy(fn_suffix, ext);
550 buildvfs_kfd const fp = kopen4loadfrommod(testfn, searchfirst);
551 if (fp != buildvfs_kfd_invalid)
552 return fp;
553 }
554
555 return buildvfs_kfd_invalid;
556 }
557
S_TryExtensionReplacements(char * const testfn,char const searchfirst,uint8_t const ismusic)558 static buildvfs_kfd S_TryExtensionReplacements(char* const testfn, char const searchfirst, uint8_t const ismusic)
559 {
560 char* extension = Bstrrchr(testfn, '.');
561 char* const fn_end = Bstrchr(testfn, '\0');
562
563 // ex: grabbag.voc --> grabbag_voc.*
564 if (extension != NULL)
565 {
566 *extension = '_';
567
568 buildvfs_kfd const fp = S_TryFormats(testfn, fn_end, searchfirst);
569 if (fp != buildvfs_kfd_invalid)
570 return fp;
571 }
572 else
573 {
574 extension = fn_end;
575 }
576
577 // ex: grabbag.mid --> grabbag.*
578 if (ismusic)
579 {
580 buildvfs_kfd const fp = S_TryFormats(testfn, extension, searchfirst);
581 if (fp != buildvfs_kfd_invalid)
582 return fp;
583 }
584
585 return buildvfs_kfd_invalid;
586 }
587
S_OpenAudio(const char * fn,char searchfirst,uint8_t const ismusic)588 buildvfs_kfd S_OpenAudio(const char* fn, char searchfirst, uint8_t const ismusic)
589 {
590 buildvfs_kfd const origfp = kopen4loadfrommod(fn, searchfirst);
591 #ifndef USE_PHYSFS
592 char const* const origparent = origfp != buildvfs_kfd_invalid ? kfileparent(origfp) : NULL;
593 uint32_t const parentlength = origparent != NULL ? Bstrlen(origparent) : 0;
594
595 auto testfn = (char*)Xmalloc(Bstrlen(fn) + 12 + parentlength); // "music/" + overestimation of parent minus extension + ".flac" + '\0'
596 #else
597 auto testfn = (char*)Xmalloc(Bstrlen(fn) + 12);
598 #endif
599
600 // look in ./
601 // ex: ./grabbag.mid
602 Bstrcpy(testfn, fn);
603 buildvfs_kfd fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
604 if (fp != buildvfs_kfd_invalid)
605 goto success;
606
607 #ifndef USE_PHYSFS
608 // look in ./music/<file's parent GRP name>/
609 // ex: ./music/duke3d/grabbag.mid
610 // ex: ./music/nwinter/grabbag.mid
611 if (origparent != NULL)
612 {
613 char const* const parentextension = Bstrrchr(origparent, '.');
614 uint32_t const namelength = parentextension != NULL ? (unsigned)(parentextension - origparent) : parentlength;
615
616 Bsprintf(testfn, "music/%.*s/%s", namelength, origparent, fn);
617 fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
618 if (fp != buildvfs_kfd_invalid)
619 goto success;
620 }
621
622 // look in ./music/
623 // ex: ./music/grabbag.mid
624 Bsprintf(testfn, "music/%s", fn);
625 fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
626 if (fp != buildvfs_kfd_invalid)
627 goto success;
628 #endif
629
630 Xfree(testfn);
631 return origfp;
632
633 success:
634 Xfree(testfn);
635 kclose(origfp);
636 return fp;
637 }
638
G_Polymer_UnInit(void)639 void G_Polymer_UnInit(void) { }
640
calc_smoothratio(ClockTicks totalclk,ClockTicks ototalclk)641 static inline int32_t calc_smoothratio(ClockTicks totalclk, ClockTicks ototalclk)
642 {
643 // if (!((ud.show_help == 0 && (!g_netServer && ud.multimode < 2) && ((g_player[myconnectindex].ps->gm & MODE_MENU) == 0)) ||
644 // (g_netServer || ud.multimode > 1) ||
645 // ud.recstat == 2) ||
646 // ud.pause_on)
647 // {
648 // return 65536;
649 // }
650
651 /* TODO
652 if (bRecord || bPlayback || nFreeze != 0 || bCamera || bPause)
653 return 65536;
654 */
655
656 int32_t rfreq = (refreshfreq != -1 ? refreshfreq : 60);
657 uint64_t elapsedFrames = tabledivide64(((uint64_t)(totalclk - ototalclk).toScale16()) * rfreq, 65536 * 120);
658 #if 0
659 //POGO: additional debug info for testing purposes
660 OSD_Printf("Elapsed frames: %" PRIu64 ", smoothratio: %" PRIu64 "\n", elapsedFrames, tabledivide64(65536 * elapsedFrames * 30, rfreq));
661 #endif
662 return clamp(tabledivide64(65536 * elapsedFrames * 30, rfreq), 0, 65536);
663 }
664
665 #endif
666