1 /*
2 This file is part of Warzone 2100.
3 Copyright (C) 1999-2004 Eidos Interactive
4 Copyright (C) 2005-2020 Warzone 2100 Project
5
6 Warzone 2100 is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Warzone 2100 is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Warzone 2100; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 /**
21 * @file init.c
22 *
23 * Game initialisation routines.
24 *
25 */
26 #include "lib/framework/frame.h"
27
28 #include <string.h>
29
30 #include "lib/framework/frameresource.h"
31 #include "lib/framework/file.h"
32 #include "lib/framework/physfs_ext.h"
33 #include "lib/framework/wzapp.h"
34 #include "lib/ivis_opengl/piemode.h"
35 #include "lib/ivis_opengl/piestate.h"
36 #include "lib/ivis_opengl/screen.h"
37 #include "lib/ivis_opengl/pieblitfunc.h"
38 #include "lib/ivis_opengl/tex.h"
39 #include "lib/ivis_opengl/imd.h"
40 #include "lib/netplay/netplay.h"
41 #include "lib/sound/audio_id.h"
42 #include "lib/sound/cdaudio.h"
43 #include "lib/sound/mixer.h"
44
45 #include "init.h"
46
47 #include "advvis.h"
48 #include "atmos.h"
49 #include "challenge.h"
50 #include "cmddroid.h"
51 #include "configuration.h"
52 #include "console.h"
53 #include "data.h"
54 #include "difficulty.h" // for "double up" and "biffer baker" cheats
55 #include "display.h"
56 #include "display3d.h"
57 #include "edit3d.h"
58 #include "effects.h"
59 #include "fpath.h"
60 #include "frend.h"
61 #include "frontend.h"
62 #include "game.h"
63 #include "gateway.h"
64 #include "hci.h"
65 #include "intdisplay.h"
66 #include "keymap.h"
67 #include "levels.h"
68 #include "lighting.h"
69 #include "loop.h"
70 #include "mapgrid.h"
71 #include "mechanics.h"
72 #include "miscimd.h"
73 #include "mission.h"
74 #include "modding.h"
75 #include "multiint.h"
76 #include "multigifts.h"
77 #include "multiplay.h"
78 #include "multistat.h"
79 #include "notifications.h"
80 #include "projectile.h"
81 #include "order.h"
82 #include "radar.h"
83 #include "research.h"
84 #include "lib/framework/cursors.h"
85 #include "text.h"
86 #include "transporter.h"
87 #include "warzoneconfig.h"
88 #include "main.h"
89 #include "wrappers.h"
90 #include "terrain.h"
91 #include "ingameop.h"
92 #include "qtscript.h"
93 #include "template.h"
94 #include "activity.h"
95
96 #include <algorithm>
97 #include <unordered_map>
98
99 static void initMiscVars();
100
101 static const char UserMusicPath[] = "music";
102
103 // FIXME Totally inappropriate place for this.
104 char fileLoadBuffer[FILE_LOAD_BUFFER_SIZE];
105
106 IMAGEFILE *FrontImages;
107
108 static wzSearchPath *searchPathRegistry = nullptr;
109
110 // Each module in the game should have a call from here to initialise
111 // any globals and statics to there default values each time the game
112 // or frontend restarts.
113 //
InitialiseGlobals()114 static bool InitialiseGlobals()
115 {
116 frontendInitVars(); // Initialise frontend globals and statics.
117 statsInitVars();
118 structureInitVars();
119 if (!messageInitVars())
120 {
121 return false;
122 }
123 if (!researchInitVars())
124 {
125 return false;
126 }
127 featureInitVars();
128 radarInitVars();
129 Edit3DInitVars();
130
131 return true;
132 }
133
134
loadLevFile(const char * filename,searchPathMode pathMode,bool ignoreWrf,char const * realFileName)135 bool loadLevFile(const char *filename, searchPathMode pathMode, bool ignoreWrf, char const *realFileName)
136 {
137 char *pBuffer;
138 UDWORD size;
139
140 if (realFileName == nullptr)
141 {
142 debug(LOG_WZ, "Loading lev file: \"%s\", builtin\n", filename);
143 }
144 else
145 {
146 debug(LOG_WZ, "Loading lev file: \"%s\" from \"%s\"\n", filename, realFileName);
147 }
148
149 if (!PHYSFS_exists(filename) || !loadFile(filename, &pBuffer, &size))
150 {
151 debug(LOG_ERROR, "File not found: %s\n", filename);
152 return false; // only in NDEBUG case
153 }
154 if (!levParse(pBuffer, size, pathMode, ignoreWrf, realFileName))
155 {
156 debug(LOG_ERROR, "Parse error in %s\n", filename);
157 free(pBuffer);
158 return false;
159 }
160 free(pBuffer);
161
162 return true;
163 }
164
165
cleanSearchPath()166 static void cleanSearchPath()
167 {
168 wzSearchPath *curSearchPath = searchPathRegistry, * tmpSearchPath = nullptr;
169
170 // Start at the lowest priority
171 while (curSearchPath->lowerPriority)
172 {
173 curSearchPath = curSearchPath->lowerPriority;
174 }
175
176 while (curSearchPath)
177 {
178 tmpSearchPath = curSearchPath->higherPriority;
179 free(curSearchPath);
180 curSearchPath = tmpSearchPath;
181 }
182 searchPathRegistry = nullptr;
183 }
184
185
186 /*!
187 * Register searchPath above the path with next lower priority
188 * For information about what can be a search path, refer to PhysFS documentation
189 */
registerSearchPath(const char path[],unsigned int priority)190 void registerSearchPath(const char path[], unsigned int priority)
191 {
192 wzSearchPath *curSearchPath = searchPathRegistry, * tmpSearchPath = nullptr;
193
194 tmpSearchPath = (wzSearchPath *)malloc(sizeof(*tmpSearchPath));
195 sstrcpy(tmpSearchPath->path, path);
196 if (path[strlen(path) - 1] != *PHYSFS_getDirSeparator())
197 {
198 sstrcat(tmpSearchPath->path, PHYSFS_getDirSeparator());
199 }
200 tmpSearchPath->priority = priority;
201
202 debug(LOG_WZ, "registerSearchPath: Registering %s at priority %i", path, priority);
203 if (!curSearchPath)
204 {
205 searchPathRegistry = tmpSearchPath;
206 searchPathRegistry->lowerPriority = nullptr;
207 searchPathRegistry->higherPriority = nullptr;
208 return;
209 }
210
211 while (curSearchPath->higherPriority && priority > curSearchPath->priority)
212 {
213 curSearchPath = curSearchPath->higherPriority;
214 }
215 while (curSearchPath->lowerPriority && priority < curSearchPath->priority)
216 {
217 curSearchPath = curSearchPath->lowerPriority;
218 }
219
220 if (priority < curSearchPath->priority)
221 {
222 tmpSearchPath->lowerPriority = curSearchPath->lowerPriority;
223 tmpSearchPath->higherPriority = curSearchPath;
224 }
225 else
226 {
227 tmpSearchPath->lowerPriority = curSearchPath;
228 tmpSearchPath->higherPriority = curSearchPath->higherPriority;
229 }
230
231 if (tmpSearchPath->lowerPriority)
232 {
233 tmpSearchPath->lowerPriority->higherPriority = tmpSearchPath;
234 }
235 if (tmpSearchPath->higherPriority)
236 {
237 tmpSearchPath->higherPriority->lowerPriority = tmpSearchPath;
238 }
239 }
240
241
242 /*!
243 * \brief Rebuilds the PHYSFS searchPath with mode specific subdirs
244 *
245 * Priority:
246 * maps > mods > base > base.wz
247 */
rebuildSearchPath(searchPathMode mode,bool force,const char * current_map)248 bool rebuildSearchPath(searchPathMode mode, bool force, const char *current_map)
249 {
250 static searchPathMode current_mode = mod_clean;
251 static std::string current_current_map;
252 wzSearchPath *curSearchPath = searchPathRegistry;
253 char tmpstr[PATH_MAX] = "\0";
254
255 if (mode != current_mode || (current_map != nullptr ? current_map : "") != current_current_map || force ||
256 (use_override_mods && override_mod_list != getModList()))
257 {
258 if (mode != mod_clean)
259 {
260 rebuildSearchPath(mod_clean, false);
261 }
262
263 current_mode = mode;
264 current_current_map = current_map != nullptr ? current_map : "";
265
266 // Start at the lowest priority
267 while (curSearchPath->lowerPriority)
268 {
269 curSearchPath = curSearchPath->lowerPriority;
270 }
271
272 switch (mode)
273 {
274 case mod_clean:
275 debug(LOG_WZ, "Cleaning up");
276 clearLoadedMods();
277
278 while (curSearchPath)
279 {
280 #ifdef DEBUG
281 debug(LOG_WZ, "Removing [%s] from search path", curSearchPath->path);
282 #endif // DEBUG
283 // Remove maps and mods
284 removeSubdirs(curSearchPath->path, "maps");
285 removeSubdirs(curSearchPath->path, "mods/music");
286 removeSubdirs(curSearchPath->path, "mods/global");
287 removeSubdirs(curSearchPath->path, "mods");
288 removeSubdirs(curSearchPath->path, "mods/autoload");
289 removeSubdirs(curSearchPath->path, "mods/campaign");
290 removeSubdirs(curSearchPath->path, "mods/multiplay");
291 removeSubdirs(curSearchPath->path, "mods/downloads");
292
293 // Remove multiplay patches
294 sstrcpy(tmpstr, curSearchPath->path);
295 sstrcat(tmpstr, "mp");
296 WZ_PHYSFS_unmount(tmpstr);
297 sstrcpy(tmpstr, curSearchPath->path);
298 sstrcat(tmpstr, "mp.wz");
299 WZ_PHYSFS_unmount(tmpstr);
300
301 // Remove plain dir
302 WZ_PHYSFS_unmount(curSearchPath->path);
303
304 // Remove base files
305 sstrcpy(tmpstr, curSearchPath->path);
306 sstrcat(tmpstr, "base");
307 WZ_PHYSFS_unmount(tmpstr);
308 sstrcpy(tmpstr, curSearchPath->path);
309 sstrcat(tmpstr, "base.wz");
310 WZ_PHYSFS_unmount(tmpstr);
311
312 // remove video search path as well
313 sstrcpy(tmpstr, curSearchPath->path);
314 sstrcat(tmpstr, "sequences.wz");
315 WZ_PHYSFS_unmount(tmpstr);
316 curSearchPath = curSearchPath->higherPriority;
317 }
318 break;
319 case mod_campaign:
320 debug(LOG_WZ, "*** Switching to campaign mods ***");
321 clearLoadedMods();
322
323 while (curSearchPath)
324 {
325 // make sure videos override included files
326 sstrcpy(tmpstr, curSearchPath->path);
327 sstrcat(tmpstr, "sequences.wz");
328 PHYSFS_mount(tmpstr, NULL, PHYSFS_APPEND);
329 curSearchPath = curSearchPath->higherPriority;
330 }
331 curSearchPath = searchPathRegistry;
332 while (curSearchPath->lowerPriority)
333 {
334 curSearchPath = curSearchPath->lowerPriority;
335 }
336 while (curSearchPath)
337 {
338 #ifdef DEBUG
339 debug(LOG_WZ, "Adding [%s] to search path", curSearchPath->path);
340 #endif // DEBUG
341 // Add global and campaign mods
342 PHYSFS_mount(curSearchPath->path, NULL, PHYSFS_APPEND);
343
344 addSubdirs(curSearchPath->path, "mods/music", PHYSFS_APPEND, nullptr, false);
345 addSubdirs(curSearchPath->path, "mods/global", PHYSFS_APPEND, use_override_mods ? &override_mods : &global_mods, true);
346 addSubdirs(curSearchPath->path, "mods", PHYSFS_APPEND, use_override_mods ? &override_mods : &global_mods, true);
347 addSubdirs(curSearchPath->path, "mods/autoload", PHYSFS_APPEND, use_override_mods ? &override_mods : nullptr, true);
348 addSubdirs(curSearchPath->path, "mods/campaign", PHYSFS_APPEND, use_override_mods ? &override_mods : &campaign_mods, true);
349 if (!WZ_PHYSFS_unmount(curSearchPath->path))
350 {
351 debug(LOG_WZ, "* Failed to remove path %s again", curSearchPath->path);
352 }
353
354 // Add plain dir
355 PHYSFS_mount(curSearchPath->path, NULL, PHYSFS_APPEND);
356
357 // Add base files
358 sstrcpy(tmpstr, curSearchPath->path);
359 sstrcat(tmpstr, "base");
360 PHYSFS_mount(tmpstr, NULL, PHYSFS_APPEND);
361 sstrcpy(tmpstr, curSearchPath->path);
362 sstrcat(tmpstr, "base.wz");
363 PHYSFS_mount(tmpstr, NULL, PHYSFS_APPEND);
364
365 curSearchPath = curSearchPath->higherPriority;
366 }
367 break;
368 case mod_multiplay:
369 debug(LOG_WZ, "*** Switching to multiplay mods ***");
370 clearLoadedMods();
371
372 while (curSearchPath)
373 {
374 // make sure videos override included files
375 sstrcpy(tmpstr, curSearchPath->path);
376 sstrcat(tmpstr, "sequences.wz");
377 PHYSFS_mount(tmpstr, NULL, PHYSFS_APPEND);
378 curSearchPath = curSearchPath->higherPriority;
379 }
380 // Add the selected map first, for mapmod support
381 if (current_map != nullptr)
382 {
383 WzString realPathAndDir = WzString::fromUtf8(PHYSFS_getRealDir(current_map)) + current_map;
384 realPathAndDir.replace("/", PHYSFS_getDirSeparator()); // Windows fix
385 PHYSFS_mount(realPathAndDir.toUtf8().c_str(), NULL, PHYSFS_APPEND);
386 }
387 curSearchPath = searchPathRegistry;
388 while (curSearchPath->lowerPriority)
389 {
390 curSearchPath = curSearchPath->lowerPriority;
391 }
392 while (curSearchPath)
393 {
394 #ifdef DEBUG
395 debug(LOG_WZ, "Adding [%s] to search path", curSearchPath->path);
396 #endif // DEBUG
397 // Add global and multiplay mods
398 PHYSFS_mount(curSearchPath->path, NULL, PHYSFS_APPEND);
399 addSubdirs(curSearchPath->path, "mods/music", PHYSFS_APPEND, nullptr, false);
400
401 // Only load if we are host or singleplayer (Initial mod load relies on this, too)
402 if (ingame.side == InGameSide::HOST_OR_SINGLEPLAYER || !NetPlay.bComms)
403 {
404 addSubdirs(curSearchPath->path, "mods/global", PHYSFS_APPEND, use_override_mods ? &override_mods : &global_mods, true);
405 addSubdirs(curSearchPath->path, "mods", PHYSFS_APPEND, use_override_mods ? &override_mods : &global_mods, true);
406 addSubdirs(curSearchPath->path, "mods/autoload", PHYSFS_APPEND, use_override_mods ? &override_mods : nullptr, true);
407 addSubdirs(curSearchPath->path, "mods/multiplay", PHYSFS_APPEND, use_override_mods ? &override_mods : &multiplay_mods, true);
408 }
409 else
410 {
411 std::vector<std::string> hashList;
412 for (Sha256 &hash : game.modHashes)
413 {
414 hashList = {hash.toString()};
415 addSubdirs(curSearchPath->path, "mods/downloads", PHYSFS_APPEND, &hashList, true);
416 }
417 }
418 WZ_PHYSFS_unmount(curSearchPath->path);
419
420 // Add multiplay patches
421 sstrcpy(tmpstr, curSearchPath->path);
422 sstrcat(tmpstr, "mp");
423 PHYSFS_mount(tmpstr, NULL, PHYSFS_APPEND);
424 sstrcpy(tmpstr, curSearchPath->path);
425 sstrcat(tmpstr, "mp.wz");
426 PHYSFS_mount(tmpstr, NULL, PHYSFS_APPEND);
427
428 // Add plain dir
429 PHYSFS_mount(curSearchPath->path, NULL, PHYSFS_APPEND);
430
431 // Add base files
432 sstrcpy(tmpstr, curSearchPath->path);
433 sstrcat(tmpstr, "base");
434 PHYSFS_mount(tmpstr, NULL, PHYSFS_APPEND);
435 sstrcpy(tmpstr, curSearchPath->path);
436 sstrcat(tmpstr, "base.wz");
437 PHYSFS_mount(tmpstr, NULL, PHYSFS_APPEND);
438
439 curSearchPath = curSearchPath->higherPriority;
440 }
441 break;
442 default:
443 debug(LOG_ERROR, "Can't switch to unknown mods %i", mode);
444 return false;
445 }
446 if (use_override_mods && mode != mod_clean)
447 {
448 if (getModList() != override_mod_list)
449 {
450 debug(LOG_POPUP, _("The required mod could not be loaded: %s\n\nWarzone will try to load the game without it."), override_mod_list.c_str());
451 }
452 clearOverrideMods();
453 current_mode = mod_override;
454 }
455
456 // User's home dir must be first so we always see what we write
457 WZ_PHYSFS_unmount(PHYSFS_getWriteDir());
458 PHYSFS_mount(PHYSFS_getWriteDir(), NULL, PHYSFS_PREPEND);
459
460 #ifdef DEBUG
461 printSearchPath();
462 #endif // DEBUG
463 }
464 else if (use_override_mods)
465 {
466 // override mods are already the same as current mods, so no need to do anything
467 clearOverrideMods();
468 }
469 return true;
470 }
471
472 struct MapFileListPath
473 {
474 public:
MapFileListPathMapFileListPath475 MapFileListPath(const std::string& platformIndependentNotation, const std::string& platformDependentNotation)
476 : platformIndependent(platformIndependentNotation)
477 , platformDependent(platformDependentNotation)
478 { }
479 std::string platformIndependent;
480 std::string platformDependent;
481 };
482 typedef std::vector<MapFileListPath> MapFileList;
listMapFiles()483 static MapFileList listMapFiles()
484 {
485 MapFileList ret, filtered;
486 std::vector<std::string> oldSearchPath;
487
488 WZ_PHYSFS_enumerateFiles("maps", [&](const char *i) -> bool {
489 std::string wzfile = i;
490 if (i[0] == '.' || wzfile.substr(wzfile.find_last_of('.') + 1) != "wz")
491 {
492 return true; // continue;
493 }
494
495 std::string realFileName_platformIndependent = std::string("maps") + "/" + i;
496 std::string realFileName_platformDependent = std::string("maps") + PHYSFS_getDirSeparator() + i;
497 ret.push_back(MapFileListPath(realFileName_platformIndependent, realFileName_platformDependent));
498 return true; // continue
499 });
500
501 // save our current search path(s)
502 debug(LOG_WZ, "Map search paths:");
503 char **searchPath = PHYSFS_getSearchPath();
504 for (char **i = searchPath; *i != nullptr; i++)
505 {
506 debug(LOG_WZ, " [%s]", *i);
507 oldSearchPath.push_back(*i);
508 WZ_PHYSFS_unmount(*i);
509 }
510 PHYSFS_freeList(searchPath);
511
512 for (const auto &realFileName : ret)
513 {
514 std::string realFilePathAndName = PHYSFS_getWriteDir() + realFileName.platformDependent;
515 if (PHYSFS_mount(realFilePathAndName.c_str(), NULL, PHYSFS_APPEND))
516 {
517 int unsafe = 0;
518 WZ_PHYSFS_enumerateFiles("multiplay/maps", [&unsafe, &realFilePathAndName](const char *file) -> bool {
519 std::string isDir = std::string("multiplay/maps/") + file;
520 if (WZ_PHYSFS_isDirectory(isDir.c_str()))
521 {
522 return true; // continue;
523 }
524 std::string checkfile = file;
525 debug(LOG_WZ, "checking ... %s", file);
526 if (checkfile.substr(checkfile.find_last_of('.') + 1) == "gam")
527 {
528 if (unsafe++ > 1)
529 {
530 debug(LOG_ERROR, "Map packs are not supported! %s NOT added.", realFilePathAndName.c_str());
531 return false; // break;
532 }
533 }
534 return true; // continue
535 });
536 if (unsafe < 2)
537 {
538 filtered.push_back(realFileName);
539 }
540 WZ_PHYSFS_unmount(realFilePathAndName.c_str());
541 }
542 else
543 {
544 debug(LOG_POPUP, "Could not mount %s, because: %s.\nPlease delete or move the file specified.", realFilePathAndName.c_str(), WZ_PHYSFS_getLastError());
545 }
546 }
547
548 // restore our search path(s) again
549 for (const auto &restorePaths : oldSearchPath)
550 {
551 PHYSFS_mount(restorePaths.c_str(), NULL, PHYSFS_APPEND);
552 }
553 debug(LOG_WZ, "Search paths restored");
554 printSearchPath();
555
556 return filtered;
557 }
558
559 // Map processing
560 struct WZmapInfo
561 {
562 bool isMapMod;
563 bool isRandom;
564 };
565
566 typedef std::string MapName;
567 typedef std::unordered_map<MapName, WZmapInfo> WZMapInfo_Map;
568 WZMapInfo_Map WZ_Maps;
569
findMap(char const * name)570 static inline WZMapInfo_Map::iterator findMap(char const *name)
571 {
572 return name != nullptr? WZ_Maps.find(name) : WZ_Maps.end();
573 }
574
CheckForMod(char const * mapFile)575 bool CheckForMod(char const *mapFile)
576 {
577 auto it = findMap(mapFile);
578 if (it != WZ_Maps.end())
579 {
580 return it->second.isMapMod;
581 }
582 if (mapFile != nullptr)
583 {
584 debug(LOG_ERROR, "Couldn't find map %s", mapFile);
585 }
586
587 return false;
588 }
589
CheckForRandom(char const * mapFile,char const * mapDataFile0)590 bool CheckForRandom(char const *mapFile, char const *mapDataFile0)
591 {
592 auto it = findMap(mapFile);
593 if (it != WZ_Maps.end())
594 {
595 return it->second.isRandom;
596 }
597
598 if (mapFile != nullptr) {
599 debug(LOG_ERROR, "Couldn't find map %s", mapFile);
600 }
601 else if (mapDataFile0 != nullptr && strlen(mapDataFile0) > 4)
602 {
603 std::string fn = mapDataFile0;
604 fn.resize(fn.size() - 4);
605 fn += "/game.js";
606 return PHYSFS_exists(fn.c_str());
607 }
608
609 return false;
610 }
611
612 // Mount the archive under the mountpoint, and enumerate the archive according to lookin
CheckInMap(const char * archive,const char * mountpoint,const std::vector<const char * > & lookin_list)613 static std::pair<bool, bool> CheckInMap(const char *archive, const char *mountpoint, const std::vector<const char *>& lookin_list)
614 {
615 bool mapmod = false;
616 bool isRandom = false;
617
618 if (!PHYSFS_mount(archive, mountpoint, PHYSFS_APPEND))
619 {
620 // We already checked to see if this was valid before, and now, something went seriously wrong.
621 debug(LOG_FATAL, "Could not mount %s, because: %s. Please delete the file, and run the game again. Game will now exit.", archive, WZ_PHYSFS_getLastError());
622 exit(-1);
623 }
624
625 for (auto lookin : lookin_list)
626 {
627 std::string checkpath = lookin;
628 checkpath.append("/");
629 WZ_PHYSFS_enumerateFiles(lookin, [&](const char *file) -> bool {
630 std::string checkfile = file;
631 if (WZ_PHYSFS_isDirectory((checkpath + checkfile).c_str()))
632 {
633 if (checkfile.compare("wrf") == 0 || checkfile.compare("stats") == 0 || checkfile.compare("components") == 0
634 || checkfile.compare("effects") == 0 || checkfile.compare("messages") == 0
635 || checkfile.compare("audio") == 0 || checkfile.compare("sequenceaudio") == 0 || checkfile.compare("misc") == 0
636 || checkfile.compare("features") == 0 || checkfile.compare("script") == 0 || checkfile.compare("structs") == 0
637 || checkfile.compare("tileset") == 0 || checkfile.compare("images") == 0 || checkfile.compare("texpages") == 0
638 || checkfile.compare("skirmish") == 0 || checkfile.compare("shaders") == 0 || checkfile.compare("fonts") == 0
639 || checkfile.compare("icons") == 0)
640 {
641 debug(LOG_WZ, "Detected: %s %s" , archive, checkfile.c_str());
642 mapmod = true;
643 return false; // break;
644 }
645 }
646 return true; // continue
647 });
648
649 std::string maps = checkpath + "/multiplay/maps";
650 WZ_PHYSFS_enumerateFiles(maps.c_str(), [&](const char *file) -> bool {
651 if (WZ_PHYSFS_isDirectory((maps + "/" + file).c_str()) && PHYSFS_exists((maps + "/" + file + "/game.js").c_str()))
652 {
653 isRandom = true;
654 return false; // break;
655 }
656 return true; // continue
657 });
658 }
659
660 if (!WZ_PHYSFS_unmount(archive))
661 {
662 debug(LOG_ERROR, "Could not unmount %s, %s", archive, WZ_PHYSFS_getLastError());
663 }
664 return {mapmod, isRandom};
665 }
666
buildMapList()667 bool buildMapList()
668 {
669 if (!loadLevFile("gamedesc.lev", mod_campaign, false, nullptr))
670 {
671 return false;
672 }
673 loadLevFile("addon.lev", mod_multiplay, false, nullptr);
674 WZ_Maps.clear();
675 MapFileList realFileNames = listMapFiles();
676 const std::vector<const char *> lookin_list = { "WZMap", "WZMap/multiplay" };
677 for (auto &realFileName : realFileNames)
678 {
679 struct WZmapInfo CurrentMap;
680 const char * pRealDirStr = PHYSFS_getRealDir(realFileName.platformIndependent.c_str());
681 if (!pRealDirStr)
682 {
683 debug(LOG_ERROR, "Failed to find realdir for: %s", realFileName.platformIndependent.c_str());
684 continue; // skip
685 }
686 std::string realFilePathAndName = pRealDirStr + realFileName.platformDependent;
687
688 PHYSFS_mount(realFilePathAndName.c_str(), NULL, PHYSFS_APPEND);
689
690 WZ_PHYSFS_enumerateFiles("", [&](const char *file) -> bool {
691 size_t len = strlen(file);
692 if (len > 10 && !strcasecmp(file + (len - 10), ".addon.lev")) // Do not add addon.lev again
693 {
694 loadLevFile(file, mod_multiplay, true, realFileName.platformIndependent.c_str());
695 }
696 // add support for X player maps using a new name to prevent conflicts.
697 if (len > 13 && !strcasecmp(file + (len - 13), ".xplayers.lev"))
698 {
699 loadLevFile(file, mod_multiplay, true, realFileName.platformIndependent.c_str());
700 }
701 return true; // continue
702 });
703
704 if (WZ_PHYSFS_unmount(realFilePathAndName.c_str()) == 0)
705 {
706 debug(LOG_ERROR, "Could not unmount %s, %s", realFilePathAndName.c_str(), WZ_PHYSFS_getLastError());
707 }
708
709 auto chk = CheckInMap(realFilePathAndName.c_str(), "WZMap", lookin_list);
710
711 const std::string& MapName = realFileName.platformIndependent;
712 CurrentMap.isMapMod = chk.first;
713 CurrentMap.isRandom = chk.second;
714 WZ_Maps.insert(WZMapInfo_Map::value_type(MapName, std::move(CurrentMap)));
715 }
716
717 return true;
718 }
719
720 // ////////////////////////////////////////////////////////////////////////////
721 // ////////////////////////////////////////////////////////////////////////////
722 // Called once on program startup.
723 //
systemInitialise(float horizScaleFactor,float vertScaleFactor)724 bool systemInitialise(float horizScaleFactor, float vertScaleFactor)
725 {
726 if (!widgInitialise())
727 {
728 return false;
729 }
730
731 if (!notificationsInitialize())
732 {
733 return false;
734 }
735
736 buildMapList();
737
738 // Initialize render engine
739 if (!pie_Initialise())
740 {
741 debug(LOG_ERROR, "Unable to initialise renderer");
742 return false;
743 }
744
745 if (!audio_Init(droidAudioTrackStopped, war_GetHRTFMode(), war_getSoundEnabled()))
746 {
747 debug(LOG_SOUND, "Continuing without audio");
748 }
749 if (war_getSoundEnabled() && war_GetMusicEnabled())
750 {
751 cdAudio_Open(UserMusicPath);
752 }
753 else
754 {
755 debug(LOG_SOUND, "Music disabled");
756 }
757
758 if (!dataInitLoadFuncs()) // Pass all the data loading functions to the framework library
759 {
760 return false;
761 }
762
763 // Initialize the iVis text rendering module
764 wzSceneBegin("Main menu loop");
765 iV_TextInit(horizScaleFactor, vertScaleFactor);
766
767 pie_InitRadar();
768
769 readAIs();
770
771 return true;
772 }
773
774 // ////////////////////////////////////////////////////////////////////////////
775 // ////////////////////////////////////////////////////////////////////////////
776 // Called once at program shutdown.
777 //
systemShutdown()778 void systemShutdown()
779 {
780 pie_ShutdownRadar();
781 clearLoadedMods();
782 flushConsoleMessages();
783
784 shutdownEffectsSystem();
785 wzSceneEnd(nullptr); // Might want to end the "Main menu loop" or "Main game loop".
786 keyMappings.clear();
787
788 // free up all the load functions (all the data should already have been freed)
789 resReleaseAll();
790
791 if (!multiShutdown()) // ajl. init net stuff
792 {
793 debug(LOG_FATAL, "Unable to multiShutdown() cleanly!");
794 abort();
795 }
796
797 // shut down various databases
798 shutdownKnownPlayers();
799
800 debug(LOG_MAIN, "shutting down audio subsystems");
801
802 debug(LOG_MAIN, "shutting down CD audio");
803 cdAudio_Close();
804
805 if (audio_Disabled() == false && !audio_Shutdown())
806 {
807 debug(LOG_FATAL, "Unable to audio_Shutdown() cleanly!");
808 abort();
809 }
810
811 debug(LOG_MAIN, "shutting down graphics subsystem");
812 levShutDown();
813 notificationsShutDown();
814 widgShutDown();
815 fpathShutdown();
816 mapShutdown();
817 debug(LOG_MAIN, "shutting down everything else");
818 pal_ShutDown(); // currently unused stub
819 frameShutDown(); // close screen / SDL / resources / cursors / trig
820 screenShutDown();
821 gfx_api::context::get().shutdown();
822 cleanSearchPath(); // clean PHYSFS search paths
823 debug_exit(); // cleanup debug routines
824 PHYSFS_deinit(); // cleanup PHYSFS (If failure, state of PhysFS is undefined, and probably badly screwed up.)
825 // NOTE: Exception handler is cleaned via atexit(ExchndlShutdown);
826 }
827
828 /***************************************************************************/
829
830 // ////////////////////////////////////////////////////////////////////////////
831 // ////////////////////////////////////////////////////////////////////////////
832 // Called At Frontend Startup.
833
frontendInitialise(const char * ResourceFile)834 bool frontendInitialise(const char *ResourceFile)
835 {
836 debug(LOG_WZ, "== Initializing frontend == : %s", ResourceFile);
837
838 if (!InitialiseGlobals()) // Initialise all globals and statics everywhere.
839 {
840 return false;
841 }
842
843 if (!stringsInitialise()) // Initialise the string system
844 {
845 return false;
846 }
847
848 if (!objInitialise()) // Initialise the object system
849 {
850 return false;
851 }
852
853 if (!allocPlayerPower()) //set up the PlayerPower for each player - this should only be done ONCE now
854 {
855 return false;
856 }
857
858 debug(LOG_MAIN, "frontEndInitialise: loading resource file .....");
859 if (!resLoad(ResourceFile, 0))
860 {
861 //need the object heaps to have been set up before loading in the save game
862 return false;
863 }
864
865 if (!dispInitialise()) // Initialise the display system
866 {
867 return false;
868 }
869
870 FrontImages = (IMAGEFILE *)resGetData("IMG", "frontend.img");
871 /* Shift the interface initialisation here temporarily so that it
872 can pick up the stats after they have been loaded */
873 if (!intInitialise())
874 {
875 return false;
876 }
877
878 // reinitialise key mappings
879 keyInitMappings(false);
880
881 // Set the default uncoloured cursor here, since it looks slightly
882 // better for menus and such.
883 wzSetCursor(CURSOR_DEFAULT);
884
885 SetFormAudioIDs(-1, ID_SOUND_WINDOWCLOSE); // disable the open noise since distorted in 3dfx builds.
886
887 initMiscVars();
888
889 gameTimeInit();
890
891 // hit me with some funky beats....
892 cdAudio_PlayTrack(SONG_FRONTEND);
893
894 return true;
895 }
896
897
frontendShutdown()898 bool frontendShutdown()
899 {
900 debug(LOG_WZ, "== Shutting down frontend ==");
901
902 saveConfig();// save settings to registry.
903
904 if (!mechanicsShutdown())
905 {
906 return false;
907 }
908
909 interfaceShutDown();
910
911 //do this before shutting down the iV library
912 resReleaseAllData();
913
914 if (!objShutdown())
915 {
916 return false;
917 }
918
919 ResearchRelease();
920
921 debug(LOG_TEXTURE, "=== frontendShutdown ===");
922 modelShutdown();
923 pie_TexShutDown();
924 pie_TexInit(); // ready for restart
925 freeComponentLists();
926 statsShutDown();
927
928 return true;
929 }
930
931
932 /******************************************************************************/
933 /* Initialisation before data is loaded */
934
935
936
stageOneInitialise()937 bool stageOneInitialise()
938 {
939 debug(LOG_WZ, "== stageOneInitialise ==");
940 wzSceneEnd("Main menu loop");
941 wzSceneBegin("Main game loop");
942
943 // Initialise all globals and statics everywhere.
944 if (!InitialiseGlobals())
945 {
946 return false;
947 }
948
949 if (!stringsInitialise()) /* Initialise the string system */
950 {
951 return false;
952 }
953
954 if (!objInitialise()) /* Initialise the object system */
955 {
956 return false;
957 }
958
959 if (!droidInit())
960 {
961 return false;
962 }
963
964 if (!initViewData())
965 {
966 return false;
967 }
968
969 if (!grpInitialise())
970 {
971 return false;
972 }
973
974 if (!aiInitialise()) /* Initialise the AI system */ // pregame
975 {
976 return false;
977 }
978
979 if (!allocPlayerPower()) /*set up the PlayerPower for each player - this should only be done ONCE now*/
980 {
981 return false;
982 }
983
984 // initialise the visibility stuff
985 if (!visInitialise())
986 {
987 return false;
988 }
989
990 if (!proj_InitSystem())
991 {
992 return false;
993 }
994
995 if (!gridInitialise())
996 {
997 return false;
998 }
999
1000 initMission();
1001 initTransporters();
1002 scriptInit();
1003
1004 gameTimeInit();
1005 transitionInit();
1006 resetScroll();
1007
1008 return true;
1009 }
1010
1011
1012 /******************************************************************************/
1013 /* Shutdown after data is released */
1014
stageOneShutDown()1015 bool stageOneShutDown()
1016 {
1017 debug(LOG_WZ, "== stageOneShutDown ==");
1018
1019 atmosSetWeatherType(WT_NONE); // reset weather and free its data
1020 wzPerfShutdown();
1021
1022 pie_FreeShaders();
1023
1024 if (audio_Disabled() == false)
1025 {
1026 sound_CheckAllUnloaded();
1027 }
1028
1029 proj_Shutdown();
1030
1031 releaseMission();
1032
1033 if (!aiShutdown())
1034 {
1035 return false;
1036 }
1037
1038 if (!objShutdown())
1039 {
1040 return false;
1041 }
1042
1043 grpShutDown();
1044
1045 ResearchRelease();
1046
1047 //free up the gateway stuff?
1048 gwShutDown();
1049
1050 shutdownTerrain();
1051
1052 if (!mapShutdown())
1053 {
1054 return false;
1055 }
1056
1057 gridShutDown();
1058
1059 debug(LOG_TEXTURE, "== stageOneShutDown ==");
1060 modelShutdown();
1061 pie_TexShutDown();
1062
1063 // Use mod_multiplay as the default (campaign might have set it to mod_singleplayer)
1064 rebuildSearchPath(mod_multiplay, true);
1065 pie_TexInit(); // restart it
1066
1067 initMiscVars();
1068 wzSceneEnd("Main game loop");
1069 wzSceneBegin("Main menu loop");
1070
1071 return true;
1072 }
1073
1074
1075 // ////////////////////////////////////////////////////////////////////////////
1076 // Initialise after the base data is loaded but before final level data is loaded
1077
stageTwoInitialise()1078 bool stageTwoInitialise()
1079 {
1080 int i;
1081
1082 debug(LOG_WZ, "== stageTwoInitialise ==");
1083
1084 // prevent "double up" and "biffer baker" cheats from messing up damage modifiers
1085 resetDamageModifiers();
1086
1087 // make sure we clear on loading; this a bad hack to fix a bug when
1088 // loading a savegame where we are building a lassat
1089 for (i = 0; i < MAX_PLAYERS; i++)
1090 {
1091 setLasSatExists(false, i);
1092 }
1093
1094 if (!dispInitialise()) /* Initialise the display system */
1095 {
1096 return false;
1097 }
1098
1099 if (!initMiscImds()) /* Set up the explosions */
1100 {
1101 debug(LOG_FATAL, "Can't find all the explosions graphics?");
1102 abort();
1103 return false;
1104 }
1105
1106 if (!cmdDroidInit())
1107 {
1108 return false;
1109 }
1110
1111 /* Shift the interface initialisation here temporarily so that it
1112 can pick up the stats after they have been loaded */
1113
1114 if (!intInitialise())
1115 {
1116 return false;
1117 }
1118
1119 if (!initMessage()) /* Initialise the message heaps */
1120 {
1121 return false;
1122 }
1123
1124 if (!gwInitialise())
1125 {
1126 return false;
1127 }
1128
1129 if (!initScripts()) // Initialise the new javascript system
1130 {
1131 return false;
1132 }
1133
1134 // reinitialise key mappings
1135 keyInitMappings(false);
1136
1137 // Set the default uncoloured cursor here, since it looks slightly
1138 // better for menus and such.
1139 wzSetCursor(CURSOR_DEFAULT);
1140
1141 SetFormAudioIDs(ID_SOUND_WINDOWOPEN, ID_SOUND_WINDOWCLOSE);
1142
1143 // Setup game queues.
1144 // Don't ask why this doesn't go in stage three. In fact, don't even ask me what stage one/two/three is supposed to mean, it seems about as descriptive as stage doStuff, stage doMoreStuff and stage doEvenMoreStuff...
1145 debug(LOG_MAIN, "Init game queues, I am %d.", selectedPlayer);
1146 sendQueuedDroidInfo(); // Discard any pending orders which could later get flushed into the game queue.
1147 for (i = 0; i < MAX_PLAYERS; ++i)
1148 {
1149 NETinitQueue(NETgameQueue(i));
1150
1151 if (!myResponsibility(i))
1152 {
1153 NETsetNoSendOverNetwork(NETgameQueue(i));
1154 }
1155 }
1156
1157 // NOTE:
1158 // - Loading savegames calls `fPathDroidRoute` (from `loadSaveDroid`) before `stageThreeInitialise`
1159 // is called, hence removing this call to `fpathInitialise` will currently break loading certain
1160 // savegames (if they interact with the fPath system).
1161 if (!fpathInitialise())
1162 {
1163 return false;
1164 }
1165
1166 debug(LOG_MAIN, "stageTwoInitialise: done");
1167
1168 return true;
1169 }
1170
1171 // ////////////////////////////////////////////////////////////////////////////
1172 // ////////////////////////////////////////////////////////////////////////////
1173 // Free up after level specific data has been released but before base data is released
1174 //
stageTwoShutDown()1175 bool stageTwoShutDown()
1176 {
1177 debug(LOG_WZ, "== stageTwoShutDown ==");
1178
1179 fpathShutdown();
1180
1181 cdAudio_Stop();
1182
1183 freeAllStructs();
1184 freeAllDroids();
1185 freeAllFeatures();
1186 freeAllFlagPositions();
1187
1188 if (!messageShutdown())
1189 {
1190 return false;
1191 }
1192
1193 if (!mechanicsShutdown())
1194 {
1195 return false;
1196 }
1197
1198
1199 if (!ShutdownRadar())
1200 {
1201 return false;
1202 }
1203
1204 interfaceShutDown();
1205
1206 cmdDroidShutDown();
1207
1208 //free up the gateway stuff?
1209 gwShutDown();
1210
1211 if (!mapShutdown())
1212 {
1213 return false;
1214 }
1215
1216 shutdown3DView();
1217
1218 return true;
1219 }
1220
stageThreeInitialise()1221 bool stageThreeInitialise()
1222 {
1223 STRUCTURE *psStr;
1224 UDWORD i;
1225 DROID *psDroid;
1226 bool fromSave = (getSaveGameType() == GTYPE_SAVE_START || getSaveGameType() == GTYPE_SAVE_MIDMISSION);
1227
1228 debug(LOG_WZ, "== stageThreeInitialise ==");
1229
1230 loopMissionState = LMS_NORMAL;
1231
1232 if (!InitRadar()) // After resLoad cause it needs the game palette initialised.
1233 {
1234 return false;
1235 }
1236
1237 // reset the clock to normal speed
1238 gameTimeResetMod();
1239
1240 if (!init3DView()) // Initialise 3d view stuff. After resLoad cause it needs the game palette initialised.
1241 {
1242 return false;
1243 }
1244
1245 effectResetUpdates();
1246 initLighting(0, 0, mapWidth, mapHeight);
1247 pie_InitLighting();
1248
1249 if (fromSave)
1250 {
1251 // these two lines are the biggest hack in the world.
1252 // the reticule seems to get detached from 'reticuleup'
1253 // this forces it back in sync...
1254 intRemoveReticule();
1255 intAddReticule();
1256 }
1257
1258 if (bMultiPlayer)
1259 {
1260 multiGameInit();
1261 initTemplates();
1262 }
1263
1264 preProcessVisibility();
1265
1266 prepareScripts(getLevelLoadType() == GTYPE_SAVE_MIDMISSION || getLevelLoadType() == GTYPE_SAVE_START);
1267
1268 if (!fpathInitialise())
1269 {
1270 return false;
1271 }
1272
1273 mapInit();
1274 gridReset();
1275
1276 //if mission screen is up, close it.
1277 if (MissionResUp)
1278 {
1279 intRemoveMissionResultNoAnim();
1280 }
1281
1282 // Re-inititialise some static variables.
1283
1284 bInTutorial = false;
1285 rangeOnScreen = false;
1286
1287 if (fromSave && ActivityManager::instance().getCurrentGameMode() == ActivitySink::GameMode::CHALLENGE)
1288 {
1289 challengeActive = true;
1290 }
1291
1292 resizeRadar();
1293
1294 setAllPauseStates(false);
1295
1296 /* decide if we have to create teams, ONLY in multiplayer mode!*/
1297 if (bMultiPlayer && alliancesSharedVision(game.alliance))
1298 {
1299 createTeamAlliances();
1300
1301 /* Update ally vision for pre-placed structures and droids */
1302 for (i = 0; i < MAX_PLAYERS; i++)
1303 {
1304 if (i != selectedPlayer)
1305 {
1306 /* Structures */
1307 for (psStr = apsStructLists[i]; psStr; psStr = psStr->psNext)
1308 {
1309 if (aiCheckAlliances(psStr->player, selectedPlayer))
1310 {
1311 visTilesUpdate((BASE_OBJECT *)psStr);
1312 }
1313 }
1314
1315 /* Droids */
1316 for (psDroid = apsDroidLists[i]; psDroid; psDroid = psDroid->psNext)
1317 {
1318 if (aiCheckAlliances(psDroid->player, selectedPlayer))
1319 {
1320 visTilesUpdate((BASE_OBJECT *)psDroid);
1321 }
1322 }
1323 }
1324 }
1325 }
1326
1327 countUpdate();
1328
1329 if (getLevelLoadType() == GTYPE_SAVE_MIDMISSION || getLevelLoadType() == GTYPE_SAVE_START)
1330 {
1331 triggerEvent(TRIGGER_GAME_LOADED);
1332 }
1333 else
1334 {
1335 if (getDebugMappingStatus())
1336 {
1337 triggerEventCheatMode(true);
1338 }
1339 triggerEvent(TRIGGER_GAME_INIT);
1340 playerBuiltHQ = structureExists(selectedPlayer, REF_HQ, true, false);
1341 }
1342
1343 // Start / randomize in-game music
1344 cdAudio_PlayTrack(SONG_INGAME);
1345
1346 return true;
1347 }
1348
1349 /*****************************************************************************/
1350 /* Shutdown before any data is released */
1351
stageThreeShutDown()1352 bool stageThreeShutDown()
1353 {
1354 debug(LOG_WZ, "== stageThreeShutDown ==");
1355
1356 setHostLaunch(HostLaunch::Normal);
1357
1358 removeSpotters();
1359
1360 // There is an asymmetry in scripts initialization and destruction, due
1361 // the many different ways scripts get loaded.
1362 if (!shutdownScripts())
1363 {
1364 return false;
1365 }
1366
1367 challengesUp = false;
1368 challengeActive = false;
1369 isInGamePopupUp = false;
1370 InGameOpUp = false;
1371 bInTutorial = false;
1372
1373 shutdownTemplates();
1374
1375 // make sure any button tips are gone.
1376 widgReset();
1377
1378 audio_StopAll();
1379
1380 if (bMultiPlayer)
1381 {
1382 multiGameShutdown();
1383 }
1384
1385 //call this here before mission data is released
1386 if (!missionShutDown())
1387 {
1388 return false;
1389 }
1390
1391 setScriptWinLoseVideo(PLAY_NONE);
1392
1393 return true;
1394 }
1395
1396 // Reset the game between campaigns
campaignReset()1397 bool campaignReset()
1398 {
1399 debug(LOG_MAIN, "campaignReset");
1400 gwShutDown();
1401 mapShutdown();
1402 shutdownTerrain();
1403 // when the terrain textures are reloaded we need to reset the radar
1404 // otherwise it will end up as a terrain texture somehow
1405 ShutdownRadar();
1406 InitRadar();
1407 return true;
1408 }
1409
1410 // Reset the game when loading a save game
saveGameReset()1411 bool saveGameReset()
1412 {
1413 debug(LOG_MAIN, "saveGameReset");
1414
1415 cdAudio_Stop();
1416
1417 freeAllStructs();
1418 freeAllDroids();
1419 freeAllFeatures();
1420 freeAllFlagPositions();
1421 initMission();
1422 initTransporters();
1423
1424 //free up the gateway stuff?
1425 gwShutDown();
1426 intResetScreen(true);
1427
1428 if (!mapShutdown())
1429 {
1430 return false;
1431 }
1432
1433 freeMessages();
1434
1435 return true;
1436 }
1437
1438 // --- Miscellaneous Initialisation stuff that really should be done each restart
initMiscVars()1439 static void initMiscVars()
1440 {
1441 selectedPlayer = 0;
1442 realSelectedPlayer = 0;
1443 godMode = false;
1444
1445 radarOnScreen = true;
1446 radarPermitted = true;
1447 allowDesign = true;
1448 includeRedundantDesigns = false;
1449 enableConsoleDisplay(true);
1450
1451 for (unsigned n = 0; n < MAX_PLAYERS; ++n)
1452 {
1453 processDebugMappings(n, false);
1454 }
1455 }
1456