1 /** @file dd_main.cpp Engine core.
2 *
3 * @todo Much of this should be refactored and merged into the App classes.
4 * @todo The rest should be split into smaller, perhaps domain-specific files.
5 *
6 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
7 * @authors Copyright © 2005-2015 Daniel Swanson <danij@dengine.net>
8 * @authors Copyright © 2006-2007 Jamie Jones <jamie_jones_au@yahoo.com.au>
9 *
10 * @par License
11 * GPL: http://www.gnu.org/licenses/gpl.html
12 *
13 * <small>This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by the
15 * Free Software Foundation; either version 2 of the License, or (at your
16 * option) any later version. This program is distributed in the hope that it
17 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
18 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
19 * Public License for more details. You should have received a copy of the GNU
20 * General Public License along with this program; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA</small>
23 */
24
25 #define DENG_NO_API_MACROS_BASE // functions defined here
26
27 #include "de_platform.h"
28 #include "dd_main.h"
29
30 #ifdef WIN32
31 # define _WIN32_DCOM
32 # include <objbase.h>
33 #endif
34 #include <cstring>
35 #ifdef UNIX
36 # include <ctype.h>
37 #endif
38
39 #include <QStringList>
40 #include <de/charsymbols.h>
41 #include <de/concurrency.h>
42 #include <de/findfile.h>
43 #include <de/memoryzone.h>
44 #include <de/memory.h>
45 #include <de/timer.h>
46 #include <de/ArrayValue>
47 #include <de/CommandLine>
48 #include <de/Garbage>
49 #include <de/NativeFile>
50 #include <de/PackageLoader>
51 #include <de/LinkFile>
52 #include <de/LogBuffer>
53 #include <de/DictionaryValue>
54 #include <de/Log>
55 #include <de/EscapeParser>
56 #include <de/NativePath>
57 #ifdef __CLIENT__
58 # include <de/texgamma.h>
59 # include <de/DisplayMode>
60 #endif
61 #include <doomsday/AbstractSession>
62 #include <doomsday/console/alias.h>
63 #include <doomsday/console/cmd.h>
64 #include <doomsday/console/exec.h>
65 #include <doomsday/console/var.h>
66 #include <doomsday/filesys/fs_main.h>
67 #include <doomsday/filesys/fs_util.h>
68 #include <doomsday/filesys/virtualmappings.h>
69 #include <doomsday/filesys/wad.h>
70 #include <doomsday/filesys/zip.h>
71 #include <doomsday/resource/databundle.h>
72 #include <doomsday/resource/manifest.h>
73 #include <doomsday/resource/resources.h>
74 #include <doomsday/res/Bundles>
75 #include <doomsday/res/DoomsdayPackage>
76 #include <doomsday/res/MapManifests>
77 #include <doomsday/res/Sprites>
78 #include <doomsday/res/Textures>
79 #include <doomsday/world/Materials>
80 #include <doomsday/help.h>
81 #include <doomsday/library.h>
82 #include <doomsday/world/entitydef.h>
83
84 #ifdef __SERVER__
85 # include "serverapp.h"
86 #endif
87
88 #include "dd_loop.h"
89 #include "def_main.h"
90 #include "busyrunner.h"
91 #include "con_config.h"
92 #include "sys_system.h"
93 //#include "ui/editors/edit_bias.h"
94 #include "gl/svg.h"
95
96 #include "world/clientserverworld.h"
97 #include "world/map.h"
98 #include "world/p_players.h"
99
100 #include "ui/infine/infinesystem.h"
101 #include "ui/nativeui.h"
102 #include "ui/progress.h"
103
104 #ifdef __CLIENT__
105 # include "clientapp.h"
106 # include "ui/clientwindowsystem.h"
107
108 # include "client/cledgeloop.h"
109 # include "client/clientsubsector.h"
110 # include "client/cl_def.h"
111 # include "client/cl_infine.h"
112
113 # include "gl/gl_main.h"
114 # include "gl/gl_defer.h"
115 # include "gl/gl_texmanager.h"
116
117 # include "network/net_main.h"
118 # include "network/net_demo.h"
119
120 # include "render/rendersystem.h"
121 # include "render/cameralensfx.h"
122 # include "render/r_draw.h" // R_InitViewWindow
123 # include "render/r_main.h" // pspOffset
124 # include "render/rend_main.h"
125 # include "render/rend_font.h"
126 # include "render/rend_particle.h" // Rend_ParticleLoadSystemTextures
127 # include "render/vr.h"
128
129 # include "Contact"
130 # include "MaterialAnimator"
131 # include "Sector"
132
133 # include "ui/ui_main.h"
134 # include "ui/inputsystem.h"
135 # include "ui/busyvisual.h"
136 # include "ui/sys_input.h"
137 # include "ui/widgets/sidebarwidget.h"
138 # include "ui/widgets/taskbarwidget.h"
139 # include "ui/home/homewidget.h"
140
141 # include "updater.h"
142 # include "updater/updatedownloaddialog.h"
143 #endif
144 #ifdef __SERVER__
145 # include "network/net_main.h"
146
147 # include "server/sv_def.h"
148 #endif
149
150 using namespace de;
151
152 class ZipFileType : public de::NativeFileType
153 {
154 public:
ZipFileType()155 ZipFileType() : NativeFileType("FT_ZIP", RC_PACKAGE)
156 {
157 addKnownExtension(".pk3");
158 addKnownExtension(".zip");
159 }
160
interpret(FileHandle & hndl,String path,FileInfo const & info) const161 File1 *interpret(FileHandle &hndl, String path, FileInfo const &info) const
162 {
163 if (Zip::recognise(hndl))
164 {
165 LOG_AS("ZipFileType");
166 LOG_RES_VERBOSE("Interpreted \"" + NativePath(path).pretty() + "\"");
167 return new Zip(hndl, path, info);
168 }
169 return nullptr;
170 }
171 };
172
173 class WadFileType : public de::NativeFileType
174 {
175 public:
WadFileType()176 WadFileType() : NativeFileType("FT_WAD", RC_PACKAGE)
177 {
178 addKnownExtension(".wad");
179 }
180
interpret(FileHandle & hndl,String path,FileInfo const & info) const181 File1 *interpret(FileHandle &hndl, String path, FileInfo const &info) const
182 {
183 if (Wad::recognise(hndl))
184 {
185 LOG_AS("WadFileType");
186 LOG_RES_VERBOSE("Interpreted \"" + NativePath(path).pretty() + "\"");
187 return new Wad(hndl, path, info);
188 }
189 return nullptr;
190 }
191 };
192
193 static dint DD_StartupWorker(void *context);
194 static dint DD_DummyWorker(void *context);
195
196 dint isDedicated;
197 dint verbose; ///< For debug messages (-verbose).
198 #ifdef __CLIENT__
199 dint symbolicEchoMode = false; ///< @note Mutable via public API.
200 #endif
201
202 static char *startupFiles = (char *) ""; ///< List of file names, whitespace seperating (written to .cfg).
203
registerResourceFileTypes()204 static void registerResourceFileTypes()
205 {
206 FileType *ftype;
207
208 //
209 // Packages types:
210 //
211 ResourceClass &packageClass = App_ResourceClass("RC_PACKAGE");
212
213 ftype = new ZipFileType();
214 packageClass.addFileType(ftype);
215 DD_AddFileType(*ftype);
216
217 ftype = new WadFileType();
218 packageClass.addFileType(ftype);
219 DD_AddFileType(*ftype);
220
221 ftype = new FileType("FT_LMP", RC_PACKAGE); ///< Treat lumps as packages so they are mapped to $App.DataPath.
222 ftype->addKnownExtension(".lmp");
223 DD_AddFileType(*ftype);
224 /// @todo ftype leaks. -jk
225
226 //
227 // Definition fileTypes:
228 //
229 ftype = new FileType("FT_DED", RC_DEFINITION);
230 ftype->addKnownExtension(".ded");
231 App_ResourceClass("RC_DEFINITION").addFileType(ftype);
232 DD_AddFileType(*ftype);
233
234 //
235 // Graphic fileTypes:
236 //
237 ResourceClass &graphicClass = App_ResourceClass("RC_GRAPHIC");
238
239 ftype = new FileType("FT_PNG", RC_GRAPHIC);
240 ftype->addKnownExtension(".png");
241 graphicClass.addFileType(ftype);
242 DD_AddFileType(*ftype);
243
244 ftype = new FileType("FT_TGA", RC_GRAPHIC);
245 ftype->addKnownExtension(".tga");
246 graphicClass.addFileType(ftype);
247 DD_AddFileType(*ftype);
248
249 ftype = new FileType("FT_JPG", RC_GRAPHIC);
250 ftype->addKnownExtension(".jpg");
251 graphicClass.addFileType(ftype);
252 DD_AddFileType(*ftype);
253
254 ftype = new FileType("FT_PCX", RC_GRAPHIC);
255 ftype->addKnownExtension(".pcx");
256 graphicClass.addFileType(ftype);
257 DD_AddFileType(*ftype);
258
259 //
260 // Model fileTypes:
261 //
262 ResourceClass &modelClass = App_ResourceClass("RC_MODEL");
263
264 ftype = new FileType("FT_DMD", RC_MODEL);
265 ftype->addKnownExtension(".dmd");
266 modelClass.addFileType(ftype);
267 DD_AddFileType(*ftype);
268
269 ftype = new FileType("FT_MD2", RC_MODEL);
270 ftype->addKnownExtension(".md2");
271 modelClass.addFileType(ftype);
272 DD_AddFileType(*ftype);
273
274 //
275 // Sound fileTypes:
276 //
277 ftype = new FileType("FT_WAV", RC_SOUND);
278 ftype->addKnownExtension(".wav");
279 App_ResourceClass("RC_SOUND").addFileType(ftype);
280 DD_AddFileType(*ftype);
281
282 //
283 // Music fileTypes:
284 //
285 ResourceClass &musicClass = App_ResourceClass("RC_MUSIC");
286
287 ftype = new FileType("FT_OGG", RC_MUSIC);
288 ftype->addKnownExtension(".ogg");
289 musicClass.addFileType(ftype);
290 DD_AddFileType(*ftype);
291
292 ftype = new FileType("FT_MP3", RC_MUSIC);
293 ftype->addKnownExtension(".mp3");
294 musicClass.addFileType(ftype);
295 DD_AddFileType(*ftype);
296
297 ftype = new FileType("FT_MOD", RC_MUSIC);
298 ftype->addKnownExtension(".mod");
299 musicClass.addFileType(ftype);
300 DD_AddFileType(*ftype);
301
302 ftype = new FileType("FT_MID", RC_MUSIC);
303 ftype->addKnownExtension(".mid");
304 musicClass.addFileType(ftype);
305 DD_AddFileType(*ftype);
306
307 //
308 // Font fileTypes:
309 //
310 ftype = new FileType("FT_DFN", RC_FONT);
311 ftype->addKnownExtension(".dfn");
312 App_ResourceClass("RC_FONT").addFileType(ftype);
313 DD_AddFileType(*ftype);
314
315 //
316 // Misc fileTypes:
317 //
318 ftype = new FileType("FT_DEH", RC_PACKAGE); ///< Treat DeHackEd patches as packages so they are mapped to $App.DataPath.
319 ftype->addKnownExtension(".deh");
320 DD_AddFileType(*ftype);
321 /// @todo ftype leaks. -jk
322 }
323
324 #if 0
325 static void createPackagesScheme()
326 {
327 FS1::Scheme &scheme = App_FileSystem().createScheme("Packages");
328
329 //
330 // Add default search paths.
331 //
332 // Note that the order here defines the order in which these paths are searched
333 // thus paths must be added in priority order (newer paths have priority).
334 //
335
336 #ifdef UNIX
337 // There may be an iwaddir specified in a system-level config file.
338 if (char *fn = UnixInfo_GetConfigValue("paths", "iwaddir"))
339 {
340 NativePath path = de::App::commandLine().startupPath() / fn;
341 scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), SearchPath::NoDescend));
342 LOG_RES_NOTE("Using paths.iwaddir: %s") << path.pretty();
343 free(fn);
344 }
345 #endif
346
347 // Add paths to games bought with/using Steam.
348 if (!CommandLine_Check("-nosteamapps"))
349 {
350 NativePath steamBase = DoomsdayApp::steamBasePath();
351 if (!steamBase.isEmpty())
352 {
353 NativePath steamPath = steamBase / "SteamApps/common/";
354 LOG_RES_NOTE("Using SteamApps path: %s") << steamPath.pretty();
355
356 static String const appDirs[] =
357 {
358 "doom 2/base",
359 "final doom/base",
360 "heretic shadow of the serpent riders/base",
361 "hexen/base",
362 "hexen deathkings of the dark citadel/base",
363 "ultimate doom/base",
364 "DOOM 3 BFG Edition/base/wads",
365 ""
366 };
367 for (dint i = 0; !appDirs[i].isEmpty(); ++i)
368 {
369 scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(steamPath / appDirs[i]),
370 SearchPath::NoDescend));
371 }
372 }
373 }
374
375 #ifdef UNIX
376 NativePath systemWads("/usr/share/games/doom");
377 if (systemWads.exists())
378 {
379 scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(systemWads),
380 SearchPath::NoDescend));
381 }
382 #endif
383
384 // Add the path from the DOOMWADDIR environment variable.
385 if (!CommandLine_Check("-nodoomwaddir") && getenv("DOOMWADDIR"))
386 {
387 NativePath path = App::commandLine().startupPath() / getenv("DOOMWADDIR");
388 scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), SearchPath::NoDescend));
389 LOG_RES_NOTE("Using DOOMWADDIR: %s") << path.pretty();
390 }
391
392 // Add any paths from the DOOMWADPATH environment variable.
393 if (!CommandLine_Check("-nodoomwadpath") && getenv("DOOMWADPATH"))
394 {
395 #if WIN32
396 # define SEP_CHAR ';'
397 #else
398 # define SEP_CHAR ':'
399 #endif
400
401 QStringList allPaths = String(getenv("DOOMWADPATH")).split(SEP_CHAR, String::SkipEmptyParts);
402 for (dint i = allPaths.count(); i--> 0; )
403 {
404 NativePath path = App::commandLine().startupPath() / allPaths[i];
405 scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), SearchPath::NoDescend));
406 LOG_RES_NOTE("Using DOOMWADPATH: %s") << path.pretty();
407 }
408
409 #undef SEP_CHAR
410 }
411
412 scheme.addSearchPath(SearchPath(de::makeUri("$(App.DataPath)/"), SearchPath::NoDescend));
413 scheme.addSearchPath(SearchPath(de::makeUri("$(App.DataPath)/$(GamePlugin.Name)/"), SearchPath::NoDescend));
414 }
415 #endif
416
DD_CreateFileSystemSchemes()417 void DD_CreateFileSystemSchemes()
418 {
419 dint const schemedef_max_searchpaths = 5;
420 struct schemedef_s {
421 char const *name;
422 char const *optOverridePath;
423 char const *optFallbackPath;
424 FS1::Scheme::Flags flags;
425 SearchPath::Flags searchPathFlags;
426 /// Priority is right to left.
427 char const *searchPaths[schemedef_max_searchpaths];
428 } defs[] = {
429 { "Defs", nullptr, nullptr, FS1::Scheme::Flag(0), 0,
430 { "$(App.DefsPath)/", "$(App.DefsPath)/$(GamePlugin.Name)/", "$(App.DefsPath)/$(GamePlugin.Name)/$(Game.IdentityKey)/" }
431 },
432 { "Graphics", "-gfxdir2", "-gfxdir", FS1::Scheme::Flag(0), 0,
433 { "$(App.DataPath)/graphics/" }
434 },
435 { "Models", "-modeldir2", "-modeldir", FS1::Scheme::MappedInPackages, 0,
436 { "$(App.DataPath)/$(GamePlugin.Name)/models/", "$(App.DataPath)/$(GamePlugin.Name)/models/$(Game.IdentityKey)/" }
437 },
438 { "Sfx", "-sfxdir2", "-sfxdir", FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
439 { "$(App.DataPath)/$(GamePlugin.Name)/sfx/", "$(App.DataPath)/$(GamePlugin.Name)/sfx/$(Game.IdentityKey)/" }
440 },
441 { "Music", "-musdir2", "-musdir", FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
442 { "$(App.DataPath)/$(GamePlugin.Name)/music/", "$(App.DataPath)/$(GamePlugin.Name)/music/$(Game.IdentityKey)/" }
443 },
444 { "Textures", "-texdir2", "-texdir", FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
445 { "$(App.DataPath)/$(GamePlugin.Name)/textures/", "$(App.DataPath)/$(GamePlugin.Name)/textures/$(Game.IdentityKey)/" }
446 },
447 { "Flats", "-flatdir2", "-flatdir", FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
448 { "$(App.DataPath)/$(GamePlugin.Name)/flats/", "$(App.DataPath)/$(GamePlugin.Name)/flats/$(Game.IdentityKey)/" }
449 },
450 { "Patches", "-patdir2", "-patdir", FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
451 { "$(App.DataPath)/$(GamePlugin.Name)/patches/", "$(App.DataPath)/$(GamePlugin.Name)/patches/$(Game.IdentityKey)/" }
452 },
453 { "LightMaps", "-lmdir2", "-lmdir", FS1::Scheme::MappedInPackages, 0,
454 { "$(App.DataPath)/$(GamePlugin.Name)/lightmaps/" }
455 },
456 { "Fonts", "-fontdir2", "-fontdir", FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
457 { "$(App.DataPath)/fonts/", "$(App.DataPath)/$(GamePlugin.Name)/fonts/", "$(App.DataPath)/$(GamePlugin.Name)/fonts/$(Game.IdentityKey)/" }
458 }
459 };
460
461 //createPackagesScheme();
462
463 // Setup the rest...
464 for (schemedef_s const &def : defs)
465 {
466 FS1::Scheme &scheme = App_FileSystem().createScheme(def.name, def.flags);
467
468 dint searchPathCount = 0;
469 while (def.searchPaths[searchPathCount] && ++searchPathCount < schemedef_max_searchpaths)
470 {}
471
472 for (dint i = 0; i < searchPathCount; ++i)
473 {
474 scheme.addSearchPath(SearchPath(de::makeUri(def.searchPaths[i]), def.searchPathFlags));
475 }
476
477 if (def.optOverridePath && CommandLine_CheckWith(def.optOverridePath, 1))
478 {
479 NativePath path = NativePath(CommandLine_NextAsPath());
480 scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), def.searchPathFlags), FS1::OverridePaths);
481 path = path / "$(Game.IdentityKey)";
482 scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), def.searchPathFlags), FS1::OverridePaths);
483 }
484
485 if (def.optFallbackPath && CommandLine_CheckWith(def.optFallbackPath, 1))
486 {
487 NativePath path = NativePath(CommandLine_NextAsPath());
488 scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), def.searchPathFlags), FS1::FallbackPaths);
489 }
490 }
491 }
492
App_Error(char const * error,...)493 void App_Error(char const *error, ...)
494 {
495 static bool errorInProgress = false;
496
497 LogBuffer_Flush();
498
499 char buff[2048], err[256];
500 va_list argptr;
501
502 #ifdef __CLIENT__
503 ClientWindow::main().eventHandler().trapMouse(false);
504 #endif
505
506 // Already in an error?
507 if (errorInProgress)
508 {
509 #ifdef __CLIENT__
510 DisplayMode_Shutdown();
511 #endif
512
513 va_start(argptr, error);
514 dd_vsnprintf(buff, sizeof(buff), error, argptr);
515 va_end(argptr);
516
517 #if defined (__CLIENT__) && defined (DENG_HAVE_BUSYRUNNER)
518 if (!ClientApp::busyRunner().inWorkerThread())
519 {
520 Sys_MessageBox(MBT_ERROR, DOOMSDAY_NICENAME, buff, 0);
521 }
522 #endif
523
524 // Exit immediately, lest we go into an infinite loop.
525 exit(1);
526 }
527
528 // We've experienced a fatal error; program will be shut down.
529 errorInProgress = true;
530
531 // Get back to the directory we started from.
532 //Dir_SetCurrent(DD_RuntimePath());
533
534 va_start(argptr, error);
535 dd_vsnprintf(err, sizeof(err), error, argptr);
536 va_end(argptr);
537
538 LOG_CRITICAL("") << err;
539 LogBuffer_Flush();
540
541 strcpy(buff, "");
542 strcat(buff, "\n");
543 strcat(buff, err);
544
545 if (BusyMode_Active())
546 {
547 DoomsdayApp::app().busyMode().abort(buff);
548
549 #if defined (__CLIENT__) && defined (DENG_HAVE_BUSYRUNNER)
550 if (ClientApp::busyRunner().inWorkerThread())
551 {
552 // We should not continue to execute the worker any more.
553 // The thread will be terminated imminently.
554 forever Thread_Sleep(10000);
555 }
556 #endif
557 }
558 else
559 {
560 App_AbnormalShutdown(buff);
561 }
562 exit(-1);
563 }
564
App_AbnormalShutdown(char const * message)565 void App_AbnormalShutdown(char const *message)
566 {
567 DENG2_ASSERT_IN_MAIN_THREAD();
568
569 #ifdef __CLIENT__
570 // This is a crash landing, better be safe than sorry.
571 DoomsdayApp::app().busyMode().setTaskRunner(nullptr);
572 #endif
573
574 Sys_Shutdown();
575
576 #ifdef __CLIENT__
577 DisplayMode_Shutdown();
578 DENG2_GUI_APP->loop().pause();
579
580 // This is an abnormal shutdown, we cannot continue drawing any of the
581 // windows. (Alternatively could hide/disable drawing of the windows.) Note
582 // that the app's event loop is running normally while we show the native
583 // message box below -- if the app windows are not hidden/closed, they might
584 // receive draw events.
585 ClientApp::windowSystem().forAll([] (BaseWindow *win) {
586 win->hide();
587 return LoopContinue;
588 });
589 #endif
590
591 if (message) // Only show if a message given.
592 {
593 // Make sure all the buffered stuff goes into the file.
594 LogBuffer_Flush();
595
596 /// @todo Get the actual output filename (might be a custom one).
597 Sys_MessageBoxWithDetailsFromFile(MBT_ERROR, DOOMSDAY_NICENAME, message,
598 "See Details for complete message log contents.",
599 LogBuffer::get().outputFile().toUtf8());
600 }
601
602 //Sys_Shutdown();
603 DD_Shutdown();
604
605 Garbage_ForgetAndLeak(); // At this point, it's too late.
606
607 // Get outta here.
608 exit(1);
609 }
610
App_AudioSystem()611 AudioSystem &App_AudioSystem()
612 {
613 if (App::appExists())
614 {
615 #ifdef __CLIENT__
616 if (ClientApp::hasAudioSystem())
617 {
618 return ClientApp::audioSystem();
619 }
620 #endif
621 #ifdef __SERVER__
622 return ServerApp::audioSystem();
623 #endif
624 }
625 throw Error("App_AudioSystem", "App not yet initialized");
626 }
627
628 #ifdef __CLIENT__
App_Resources()629 ClientResources &App_Resources()
630 {
631 return ClientResources::get();
632 }
633 #else
App_Resources()634 Resources &App_Resources()
635 {
636 return Resources::get();
637 }
638 #endif
639
App_World()640 ClientServerWorld &App_World()
641 {
642 return static_cast<ClientServerWorld &>(World::get());
643 }
644
App_InFineSystem()645 InFineSystem &App_InFineSystem()
646 {
647 if (App::appExists())
648 {
649 #ifdef __CLIENT__
650 return ClientApp::infineSystem();
651 #endif
652 #ifdef __SERVER__
653 return ServerApp::infineSystem();
654 #endif
655 }
656 throw Error("App_InFineSystem", "App not yet initialized");
657 }
658
659 #undef Con_Open
Con_Open(dint yes)660 void Con_Open(dint yes)
661 {
662 #ifdef __CLIENT__
663 if (yes)
664 {
665 ClientWindow &win = ClientWindow::main();
666 win.taskBar().open();
667 win.root().setFocus(&win.console().commandLine());
668 }
669 else
670 {
671 ClientWindow::main().console().closeLog();
672 }
673 #endif
674
675 #ifdef __SERVER__
676 DENG2_UNUSED(yes);
677 #endif
678 }
679
680 #ifdef __CLIENT__
681
682 /**
683 * Console command to open/close the console prompt.
684 */
D_CMD(OpenClose)685 D_CMD(OpenClose)
686 {
687 DENG2_UNUSED2(src, argc);
688
689 if (!stricmp(argv[0], "conopen"))
690 {
691 Con_Open(true);
692 }
693 else if (!stricmp(argv[0], "conclose"))
694 {
695 Con_Open(false);
696 }
697 else
698 {
699 Con_Open(!ClientWindow::main().console().isLogOpen());
700 }
701 return true;
702 }
703
D_CMD(TaskBar)704 D_CMD(TaskBar)
705 {
706 DENG2_UNUSED3(src, argc, argv);
707
708 ClientWindow &win = ClientWindow::main();
709 if (!win.taskBar().isOpen() || !win.console().commandLine().hasFocus())
710 {
711 win.taskBar().open();
712 win.console().focusOnCommandLine();
713 }
714 else
715 {
716 win.taskBar().close();
717 }
718 return true;
719 }
720
D_CMD(PackagesSidebar)721 D_CMD(PackagesSidebar)
722 {
723 DENG2_UNUSED3(src, argc, argv);
724
725 if (!DoomsdayApp::isGameLoaded()) return false;
726
727 ClientWindow &win = ClientWindow::main();
728 if (!win.hasSidebar())
729 {
730 win.taskBar().openPackagesSidebar();
731 }
732 else
733 {
734 win.sidebar().as<SidebarWidget>().close();
735 }
736 return true;
737 }
738
D_CMD(Tutorial)739 D_CMD(Tutorial)
740 {
741 DENG2_UNUSED3(src, argc, argv);
742 ClientWindow::main().taskBar().showTutorial();
743 return true;
744 }
745
746 #endif // __CLIENT__
747
DD_ActivateGameWorker(void * context)748 int DD_ActivateGameWorker(void *context)
749 {
750 DoomsdayApp::GameChangeParameters &parms = *(DoomsdayApp::GameChangeParameters *) context;
751
752 auto &plugins = DoomsdayApp::plugins();
753 auto &resSys = App_Resources();
754
755 // Some resources types are located prior to initializing the game.
756 auto &textures = res::Textures::get();
757 textures.initTextures();
758 textures.textureScheme("Lightmaps").clear();
759 textures.textureScheme("Flaremaps").clear();
760 resSys.mapManifests().initMapManifests();
761
762 if (parms.initiatedBusyMode)
763 {
764 Con_SetProgress(50);
765 }
766
767 // Now that resources have been located we can begin to initialize the game.
768 if (App_GameLoaded())
769 {
770 // Any game initialization hooks?
771 plugins.callAllHooks(HOOK_GAME_INIT);
772
773 if (gx.PreInit)
774 {
775 DENG2_ASSERT(App_CurrentGame().pluginId() != 0);
776
777 plugins.setActivePluginId(App_CurrentGame().pluginId());
778 gx.PreInit(App_CurrentGame().id().toUtf8());
779 plugins.setActivePluginId(0);
780 }
781 }
782
783 if (parms.initiatedBusyMode)
784 {
785 Con_SetProgress(100);
786 }
787
788 if (App_GameLoaded())
789 {
790 File const *configFile;
791
792 // Parse the game's main config file.
793 // If a custom top-level config is specified; let it override.
794 if (CommandLine_CheckWith("-config", 1))
795 {
796 Con_ParseCommands(NativePath(CommandLine_NextAsPath()));
797 }
798 else
799 {
800 configFile = FS::tryLocate<const File>(App_CurrentGame().mainConfig());
801 Con_SetDefaultPath(App_CurrentGame().mainConfig());
802
803 // This will be missing on the first launch.
804 if (configFile)
805 {
806 LOG_SCR_NOTE("Parsing primary config %s...") << configFile->description();
807 Con_ParseCommands(*configFile);
808 }
809 }
810 Con_SetAllowed(CPCF_ALLOW_SAVE_STATE);
811
812 #ifdef __CLIENT__
813 // Apply default control bindings for this game.
814 ClientApp::inputSystem().bindGameDefaults();
815
816 // Read bindings for this game and merge with the working set.
817 if ((configFile = FS::tryLocate<const File>(App_CurrentGame().bindingConfig()))
818 != nullptr)
819 {
820 Con_ParseCommands(*configFile);
821 }
822 Con_SetAllowed(CPCF_ALLOW_SAVE_BINDINGS);
823 #endif
824 }
825
826 if (parms.initiatedBusyMode)
827 {
828 Con_SetProgress(120);
829 }
830
831 Def_Read();
832
833 if (parms.initiatedBusyMode)
834 {
835 Con_SetProgress(130);
836 }
837
838 resSys.sprites().initSprites(); // Fully initialize sprites.
839 #ifdef __CLIENT__
840 resSys.initModels();
841 #endif
842
843 Def_PostInit();
844
845 DD_ReadGameHelp();
846
847 // Reset the tictimer so than any fractional accumulation is not added to
848 // the tic/game timer of the newly-loaded game.
849 gameTime = 0;
850 DD_ResetTimer();
851
852 #ifdef __CLIENT__
853 // Make sure that the next frame does not use a filtered viewer.
854 R_ResetViewer();
855 #endif
856
857 // Init player values.
858 DoomsdayApp::players().forAll([] (Player &plr)
859 {
860 plr.extraLight = 0;
861 plr.targetExtraLight = 0;
862 plr.extraLightCounter = 0;
863 return LoopContinue;
864 });
865
866 if (gx.PostInit)
867 {
868 plugins.setActivePluginId(App_CurrentGame().pluginId());
869 gx.PostInit();
870 plugins.setActivePluginId(0);
871 }
872
873 if (parms.initiatedBusyMode)
874 {
875 Con_SetProgress(200);
876 }
877
878 return 0;
879 }
880
App_Games()881 Games &App_Games()
882 {
883 if (App::appExists())
884 {
885 #ifdef __CLIENT__
886 return ClientApp::games();
887 #endif
888 #ifdef __SERVER__
889 return ServerApp::games();
890 #endif
891 }
892 throw Error("App_Games", "App not yet initialized");
893 }
894
App_ClearGames()895 void App_ClearGames()
896 {
897 App_Games().clear();
898 DoomsdayApp::setGame(App_Games().nullGame());
899 }
900
populateGameInfo(GameInfo & info,Game const & game)901 static void populateGameInfo(GameInfo &info, Game const &game)
902 {
903 info.identityKey = AutoStr_FromTextStd(game.id().toUtf8().constData());
904 info.title = AutoStr_FromTextStd(game.title().toUtf8().constData());
905 info.author = AutoStr_FromTextStd(game.author().toUtf8().constData());
906 }
907
908 /// @note Part of the Doomsday public API.
909 #undef DD_GameInfo
DD_GameInfo(GameInfo * info)910 dd_bool DD_GameInfo(GameInfo *info)
911 {
912 LOG_AS("DD_GameInfo");
913 if (!info) return false;
914
915 zapPtr(info);
916
917 if (App_GameLoaded())
918 {
919 populateGameInfo(*info, App_CurrentGame());
920 return true;
921 }
922
923 LOGDEV_WARNING("No game currently loaded");
924 return false;
925 }
926
App_CurrentGame()927 Game const &App_CurrentGame()
928 {
929 return DoomsdayApp::game();
930 }
931
932 static GameProfile automaticProfile;
933
autoselectGameProfile()934 static GameProfile const *autoselectGameProfile()
935 {
936 if (auto arg = CommandLine::get().check("-game", 1))
937 {
938 String const param = arg.params.first();
939 Games &games = DoomsdayApp::games();
940
941 // The argument can be a game ID or a profile name.
942 if (games.contains(param))
943 {
944 auto &prof = DoomsdayApp::gameProfiles().find(games[param].title()).as<GameProfile>();
945 prof.setLastPlayedAt();
946 automaticProfile = prof;
947 }
948 else if (auto *prof = maybeAs<GameProfile>(DoomsdayApp::gameProfiles().tryFind(param)))
949 {
950 prof->setLastPlayedAt();
951 automaticProfile = *prof;
952 }
953
954 // Packages from the command line.
955 foreach (String packageId, PackageLoader::get().loadedFromCommandLine())
956 {
957 StringList pkgs = automaticProfile.packages();
958 pkgs << packageId;
959 automaticProfile.setPackages(pkgs);
960 }
961
962 // Also append the packages specified as files on the command line.
963 foreach (File *f, DoomsdayApp::app().filesFromCommandLine())
964 {
965 String packageId;
966 if (auto const *bundle = maybeAs<DataBundle>(f))
967 {
968 packageId = bundle->packageId();
969 }
970 else if (f->extension() == ".pack")
971 {
972 packageId = Package::identifierForFile(*f);
973 }
974 else
975 {
976 LOG_RES_WARNING("Unknown file %s will not be loaded")
977 << f->description();
978 }
979
980 if (!packageId.isEmpty())
981 {
982 StringList pkgs = automaticProfile.packages();
983 pkgs << packageId;
984 automaticProfile.setPackages(pkgs);
985 }
986 }
987
988 if (automaticProfile.isPlayable())
989 {
990 return &automaticProfile;
991 }
992 }
993
994 // We don't know what to do.
995 return nullptr;
996 }
997
DD_EarlyInit()998 dint DD_EarlyInit()
999 {
1000 // Determine the requested degree of verbosity.
1001 ::verbose = CommandLine_Exists("-verbose");
1002
1003 #ifdef __SERVER__
1004 ::isDedicated = true;
1005 #else
1006 ::isDedicated = false;
1007 #endif
1008
1009 // Bring the console online as soon as we can.
1010 DD_ConsoleInit();
1011 Con_InitDatabases();
1012
1013 // Register the engine's console commands and variables.
1014 DD_ConsoleRegister();
1015
1016 return true;
1017 }
1018
1019 // Perform basic runtime type size checks.
1020 #ifdef DENG2_DEBUG
assertTypeSizes()1021 static void assertTypeSizes()
1022 {
1023 void *ptr = 0;
1024 int32_t int32 = 0;
1025 int16_t int16 = 0;
1026 dfloat float32 = 0;
1027
1028 DENG2_UNUSED(ptr);
1029 DENG2_UNUSED(int32);
1030 DENG2_UNUSED(int16);
1031 DENG2_UNUSED(float32);
1032
1033 ASSERT_32BIT(int32);
1034 ASSERT_16BIT(int16);
1035 ASSERT_32BIT(float32);
1036 #ifdef __64BIT__
1037 ASSERT_64BIT(ptr);
1038 ASSERT_64BIT(int64_t);
1039 #else
1040 ASSERT_NOT_64BIT(ptr);
1041 #endif
1042 }
1043 #endif
1044
1045 /**
1046 * Engine initialization. Once completed the game loop is ready to be started.
1047 * Called from the app entrypoint function.
1048 */
initializeWithWindowReady()1049 static void initializeWithWindowReady()
1050 {
1051 DENG2_DEBUG_ONLY( assertTypeSizes(); )
1052
1053 static char const *AUTOEXEC_NAME = "autoexec.cfg";
1054
1055 #ifdef __CLIENT__
1056 GL_EarlyInit();
1057 #endif
1058
1059 // Initialize the subsystems needed prior to entering busy mode for the first time.
1060 Sys_Init();
1061 ResourceClass::setResourceClassCallback(App_ResourceClass);
1062 registerResourceFileTypes();
1063 F_Init();
1064 DD_CreateFileSystemSchemes();
1065
1066 #ifdef __CLIENT__
1067 FR_Init();
1068
1069 // Enter busy mode until startup complete.
1070 //Con_InitProgress(200);
1071 #endif
1072
1073 BusyMode_RunNewTaskWithName(BUSYF_NO_UPLOADS | BUSYF_STARTUP | (verbose? BUSYF_CONSOLE_OUTPUT : 0),
1074 DD_StartupWorker, 0, "Starting up...");
1075
1076 // Engine initialization is complete. Now finish up with the GL.
1077 #ifdef __CLIENT__
1078 GL_Init();
1079 GL_InitRefresh();
1080 App_Resources().clearAllTextureSpecs();
1081 LensFx_Init();
1082 R_InitViewWindow();
1083 UI_LoadFonts();
1084 #endif
1085 App_Resources().initSystemTextures();
1086
1087 //#ifdef __CLIENT__
1088 // // Do deferred uploads.
1089 // Con_SetProgress(100);
1090 //#endif
1091 BusyMode_RunNewTaskWithName(BUSYF_STARTUP | (verbose? BUSYF_CONSOLE_OUTPUT : 0),
1092 DD_DummyWorker, 0, "Buffering...");
1093
1094 //
1095 // Try to locate all required data files for all registered games.
1096 //
1097 //#ifdef __CLIENT__
1098 // Con_SetProgress(200);
1099 //#endif
1100
1101 App_Games().checkReadiness();
1102
1103 // Attempt automatic game selection.
1104 if (!CommandLine_Exists("-noautoselect") || isDedicated)
1105 {
1106 if (GameProfile const *game = autoselectGameProfile())
1107 {
1108 #ifdef __CLIENT__
1109 ClientWindow::main().home().moveOffscreen(0.0);
1110 #endif
1111 // Begin the game session.
1112 DoomsdayApp::app().changeGame(*game, DD_ActivateGameWorker);
1113 }
1114 #ifdef __SERVER__
1115 else
1116 {
1117 // A server is presently useless without a game, as shell
1118 // connections can only be made after a game is loaded and the
1119 // server mode started.
1120 /// @todo Allow shell connections in Home, too.
1121 String msg = "Could not determine which game to start. "
1122 "Please specify one with the " _E(b) "-game" _E(.) " option. ";
1123 auto const playable = DoomsdayApp::gameProfiles().allPlayableProfiles();
1124 if (playable.isEmpty())
1125 {
1126 msg += "However, it seems all games are missing required data files. "
1127 "Check that the " _E(b) "-iwad" _E(.) " option specifies a "
1128 "folder with game WAD files.";
1129 }
1130 else
1131 {
1132 StringList ids;
1133 foreach (GameProfile const *prof, playable) ids << prof->gameId();
1134 msg += "The following games are playable: " + String::join(ids, ", ");
1135 }
1136 App_Error(msg.toLatin1());
1137 }
1138 #endif
1139 }
1140
1141 FS_InitPathLumpMappings();
1142
1143 // Re-initialize the filesystem subspace schemes as there are now new
1144 // resources to be found on existing search paths (probably that is).
1145 App_FileSystem().resetAllSchemes();
1146
1147 //
1148 // One-time execution of various command line features available during startup.
1149 //
1150 if (CommandLine_CheckWith("-dumplump", 1))
1151 {
1152 String name = CommandLine_Next();
1153 lumpnum_t lumpNum = App_FileSystem().lumpNumForName(name);
1154 if (lumpNum >= 0)
1155 {
1156 F_DumpFile(App_FileSystem().lump(lumpNum), 0);
1157 }
1158 else
1159 {
1160 LOG_RES_WARNING("Cannot dump unknown lump \"%s\"") << name;
1161 }
1162 }
1163
1164 if (CommandLine_Check("-dumpwaddir"))
1165 {
1166 Con_Executef(CMDS_CMDLINE, false, "listlumps");
1167 }
1168
1169 // Try to load the autoexec file. This is done here to make sure everything is
1170 // initialized: the user can do here anything that s/he'd be able to do in-game
1171 // provided a game was loaded during startup.
1172 Con_ParseCommands(App::app().nativeHomePath() / AUTOEXEC_NAME);
1173
1174 // Read additional config files that should be processed post engine init.
1175 if (CommandLine_CheckWith("-parse", 1))
1176 {
1177 LOG_AS("-parse");
1178 Time begunAt;
1179 forever
1180 {
1181 char const *arg = CommandLine_NextAsPath();
1182 if (!arg || arg[0] == '-') break;
1183
1184 LOG_NOTE("Additional pre-init config file \"%s\"") << NativePath(arg).pretty();
1185 Con_ParseCommands(NativePath(arg));
1186 }
1187 LOGDEV_SCR_VERBOSE("Completed in %.2f seconds") << begunAt.since();
1188 }
1189
1190 // A console command on the command line?
1191 for (dint p = 1; p < CommandLine_Count() - 1; p++)
1192 {
1193 if (stricmp(CommandLine_At(p), "-command") &&
1194 stricmp(CommandLine_At(p), "-cmd"))
1195 {
1196 continue;
1197 }
1198
1199 for (++p; p < CommandLine_Count(); p++)
1200 {
1201 char const *arg = CommandLine_At(p);
1202
1203 if (arg[0] == '-')
1204 {
1205 p--;
1206 break;
1207 }
1208 Con_Execute(CMDS_CMDLINE, arg, false, false);
1209 }
1210 }
1211
1212 //
1213 // One-time execution of network commands on the command line.
1214 // Commands are only executed if we have loaded a game during startup.
1215 //
1216 if (App_GameLoaded())
1217 {
1218 // Client connection command.
1219 if (CommandLine_CheckWith("-connect", 1))
1220 {
1221 Con_Executef(CMDS_CMDLINE, false, "connect %s", CommandLine_Next());
1222 }
1223
1224 // Incoming TCP port.
1225 if (CommandLine_CheckWith("-port", 1))
1226 {
1227 Con_Executef(CMDS_CMDLINE, false, "net-ip-port %s", CommandLine_Next());
1228 }
1229
1230 #ifdef __SERVER__
1231 // Automatically start the server.
1232 N_ServerOpen();
1233 #endif
1234 }
1235 else
1236 {
1237 // No game loaded.
1238 // Lets get most of everything else initialized.
1239 // Reset file IDs so previously seen files can be processed again.
1240 App_FileSystem().resetFileIds();
1241 FS_InitPathLumpMappings();
1242 FS_InitVirtualPathMappings();
1243 App_FileSystem().resetAllSchemes();
1244
1245 auto &textures = res::Textures::get();
1246 textures.initTextures();
1247 textures.textureScheme("Lightmaps").clear();
1248 textures.textureScheme("Flaremaps").clear();
1249 App_Resources().mapManifests().initMapManifests();
1250
1251 Def_Read();
1252
1253 App_Resources().sprites().initSprites();
1254 #ifdef __CLIENT__
1255 App_Resources().initModels();
1256 #endif
1257
1258 Def_PostInit();
1259
1260 if (!CommandLine_Exists("-noautoselect"))
1261 {
1262 LOG_NOTE("Game could not be selected automatically");
1263 }
1264 }
1265 }
1266
1267 /**
1268 * This gets called when the main window is ready for GL init. The application
1269 * event loop is already running.
1270 */
DD_FinishInitializationAfterWindowReady()1271 void DD_FinishInitializationAfterWindowReady()
1272 {
1273 LOGDEV_MSG("Window is ready, finishing initialization");
1274
1275 #ifdef __CLIENT__
1276 # ifdef WIN32
1277 // Now we can get the color transfer table as the window is available.
1278 DisplayMode_SaveOriginalColorTransfer();
1279 # endif
1280 if (!Sys_GLInitialize())
1281 {
1282 App_Error("Error initializing OpenGL.\n");
1283 }
1284 else
1285 {
1286 ClientWindow::main().setTitle(DD_ComposeMainWindowTitle());
1287 }
1288 #endif // __CLIENT__
1289
1290 // Initialize engine subsystems and initial state.
1291 try
1292 {
1293 initializeWithWindowReady();
1294 // Let everyone know we're up and running (via another timer callback).
1295 Loop::get().timer(0.1, [] () { App::app().notifyStartupComplete(); });
1296 return;
1297 }
1298 catch (Error const &er)
1299 {
1300 EscapeParser esc;
1301 esc.parse(er.asText());
1302 Sys_CriticalMessage(esc.plainText().toUtf8().constData());
1303 }
1304 catch (...)
1305 {}
1306 exit(2); // Cannot continue...
1307 }
1308
DD_StartupWorker(void *)1309 static dint DD_StartupWorker(void * /*context*/)
1310 {
1311 #ifdef WIN32
1312 // Initialize COM for this thread (needed for DirectInput).
1313 CoInitialize(nullptr);
1314 #endif
1315 //Con_SetProgress(10);
1316
1317 // Any startup hooks?
1318 DoomsdayApp::plugins().callAllHooks(HOOK_STARTUP);
1319 //Con_SetProgress(20);
1320
1321 // Was the change to userdir OK?
1322 /*if (CommandLine_CheckWith("-userdir", 1) && !DoomsdayApp::app().isUsingUserDir())
1323 {
1324 LOG_WARNING("User directory not found (check -userdir)");
1325 }*/
1326
1327 FS_InitVirtualPathMappings();
1328 App_FileSystem().resetAllSchemes();
1329
1330 //Con_SetProgress(40);
1331
1332 Net_Init();
1333 Sys_HideMouseCursor();
1334
1335 // Read config files that should be read BEFORE engine init.
1336 if (CommandLine_CheckWith("-cparse", 1))
1337 {
1338 Time begunAt;
1339 LOG_AS("-cparse")
1340
1341 forever
1342 {
1343 char const *arg = CommandLine_NextAsPath();
1344 if (!arg || arg[0] == '-') break;
1345
1346 LOG_MSG("Additional (pre-init) config file \"%s\"") << NativePath(arg).pretty();
1347 Con_ParseCommands(arg);
1348 }
1349 LOGDEV_SCR_VERBOSE("Completed in %.2f seconds") << begunAt.since();
1350 }
1351
1352 //
1353 // Add required engine resource files.
1354 //
1355
1356 // Make sure all files have been found so we can determine which games are playable.
1357 /*Folder::waitForPopulation(
1358 #if defined (__SERVER__)
1359 Folder::BlockingMainThread
1360 #endif
1361 );
1362 DoomsdayApp::bundles().waitForEverythingIdentified();*/
1363 FS::waitForIdle();
1364
1365 /*String foundPath = App_FileSystem().findPath(de::Uri("doomsday.pk3", RC_PACKAGE),
1366 RLF_DEFAULT, App_ResourceClass(RC_PACKAGE));
1367 foundPath = App_BasePath() / foundPath; // Ensure the path is absolute.
1368 File1 *loadedFile = File1::tryLoad(de::makeUri(foundPath));
1369 DENG2_ASSERT(loadedFile);
1370 DENG2_UNUSED(loadedFile);*/
1371
1372 // It is assumed that doomsday.pk3 is currently stored in a native file.
1373 if (File const *basePack = App::packageLoader().select("net.dengine.legacy.base"))
1374 {
1375 // The returned file is a symlink to the actual data file.
1376 // Since we're loading with FS1, we need to look up the native path.
1377 // The data file is an interpreter in /local/wads, whose source is the native file.
1378 File1::tryLoad(File1::LoadAsVanillaFile,
1379 res::DoomsdayPackage::loadableUri(*basePack));
1380 }
1381 else
1382 {
1383 throw Error("DD_StartupWorker", "Failed to find \"net.dengine.legacy.base\" package");
1384 }
1385
1386 // No more files or packages will be loaded in "startup mode" after this point.
1387 App_FileSystem().endStartup();
1388
1389 // Load engine help resources.
1390 DD_InitHelp();
1391 //Con_SetProgress(60);
1392
1393 // Execute the startup script (Startup.cfg).
1394 char const *startupConfig = "startup.cfg";
1395 if (F_FileExists(startupConfig))
1396 {
1397 Con_ParseCommands(startupConfig);
1398 }
1399 //Con_SetProgress(90);
1400
1401 #ifdef __CLIENT__
1402 R_BuildTexGammaLut(texGamma);
1403 R_InitSvgs();
1404 R_ResetFrameCount();
1405 #endif
1406 //Con_SetProgress(165);
1407
1408 Net_InitGame();
1409 #ifdef __CLIENT__
1410 Demo_Init();
1411 #endif
1412 //Con_SetProgress(190);
1413
1414 // In dedicated mode the console must be opened, so all input events
1415 // will be handled by it.
1416 if (isDedicated)
1417 {
1418 Con_Open(true);
1419 }
1420 //Con_SetProgress(199);
1421
1422 // Any initialization hooks?
1423 DoomsdayApp::plugins().callAllHooks(HOOK_INIT);
1424 //Con_SetProgress(200);
1425
1426 // Release all cached uncompressed entries. If the contents of the compressed
1427 // files are needed, they will be decompressed and cached again.
1428 DoomsdayApp::app().uncacheFilesFromMemory();
1429
1430 #ifdef WIN32
1431 // This thread has finished using COM.
1432 CoUninitialize();
1433 #endif
1434
1435 return 0;
1436 }
1437
1438 #if 1
1439 /**
1440 * This only exists so we have something to call while the deferred uploads of the
1441 * startup are processed.
1442 */
DD_DummyWorker(void *)1443 static dint DD_DummyWorker(void * /*context*/)
1444 {
1445 Con_SetProgress(200);
1446 return 0;
1447 }
1448 #endif
1449
DD_CheckTimeDemo()1450 void DD_CheckTimeDemo()
1451 {
1452 static bool checked = false;
1453
1454 if (!checked)
1455 {
1456 checked = true;
1457 if (CommandLine_CheckWith("-timedemo", 1) || // Timedemo mode.
1458 CommandLine_CheckWith("-playdemo", 1)) // Play-once mode.
1459 {
1460 Block cmd = String("playdemo %1").arg(CommandLine_Next()).toUtf8();
1461 Con_Execute(CMDS_CMDLINE, cmd.constData(), false, false);
1462 }
1463 }
1464 }
1465
DD_UpdateEngineStateWorker(void * context)1466 static dint DD_UpdateEngineStateWorker(void *context)
1467 {
1468 DENG2_ASSERT(context);
1469 auto const initiatedBusyMode = *static_cast<bool *>(context);
1470
1471 #ifdef __CLIENT__
1472 if (!novideo)
1473 {
1474 GL_InitRefresh();
1475 App_Resources().clearAllTextureSpecs();
1476 }
1477 #endif
1478 App_Resources().initSystemTextures();
1479
1480 if (initiatedBusyMode)
1481 {
1482 Con_SetProgress(50);
1483 }
1484
1485 // Allow previously seen files to be processed again.
1486 App_FileSystem().resetFileIds();
1487
1488 // Re-read definitions.
1489 Def_Read();
1490
1491 //
1492 // Rebuild resource data models (defs might've changed).
1493 //
1494 App_Resources().sprites().initSprites();
1495 #ifdef __CLIENT__
1496 App_Resources().clearAllRawTextures();
1497 App_Resources().initModels();
1498 #endif
1499 Def_PostInit();
1500
1501 //
1502 // Update misc subsystems.
1503 //
1504 App_World().update();
1505
1506 #ifdef __CLIENT__
1507 // Recalculate the light range mod matrix.
1508 Rend_UpdateLightModMatrix();
1509 // The rendering lists have persistent data that has changed during the
1510 // re-initialization.
1511 ClientApp::renderSystem().clearDrawLists();
1512 #endif
1513
1514 /// @todo fixme: Update the game title and the status.
1515
1516 #ifdef DENG2_DEBUG
1517 Z_CheckHeap();
1518 #endif
1519
1520 if (initiatedBusyMode)
1521 {
1522 Con_SetProgress(200);
1523 }
1524 return 0;
1525 }
1526
DD_UpdateEngineState()1527 void DD_UpdateEngineState()
1528 {
1529 LOG_MSG("Updating engine state...");
1530
1531 #ifdef __CLIENT__
1532 // Stop playing sounds and music.
1533 App_AudioSystem().reset();
1534
1535 BusyMode_FreezeGameForBusyMode();
1536 GL_SetFilter(false);
1537 Demo_StopPlayback();
1538 Rend_ResetLookups();
1539 #endif
1540
1541 //App_FileSystem().resetFileIds();
1542
1543 // Update the dir/WAD translations.
1544 FS_InitPathLumpMappings();
1545 FS_InitVirtualPathMappings();
1546 // Re-build the filesystem subspace schemes as there may be new resources to be found.
1547 App_FileSystem().resetAllSchemes();
1548
1549 res::Textures::get().initTextures();
1550 Resources::get().mapManifests().initMapManifests();
1551
1552 if (App_GameLoaded() && gx.UpdateState)
1553 {
1554 gx.UpdateState(DD_PRE);
1555 }
1556
1557 #ifdef __CLIENT__
1558 dd_bool hadFog = fogParams.usingFog;
1559
1560 GL_TotalReset();
1561 GL_TotalRestore(); // Bring GL back online.
1562
1563 // Make sure the fog is enabled, if necessary.
1564 if (hadFog)
1565 {
1566 GL_UseFog(true);
1567 }
1568 #endif
1569
1570 // The bulk of this we can do in busy mode unless we are already busy
1571 // (which can happen during a runtime game change).
1572 bool initiatedBusyMode = !BusyMode_Active();
1573 if (initiatedBusyMode)
1574 {
1575 #ifdef __CLIENT__
1576 Con_InitProgress(200);
1577 #endif
1578 BusyMode_RunNewTaskWithName(BUSYF_ACTIVITY | BUSYF_PROGRESS_BAR | (verbose? BUSYF_CONSOLE_OUTPUT : 0),
1579 DD_UpdateEngineStateWorker, &initiatedBusyMode,
1580 "Updating engine state...");
1581 }
1582 else
1583 {
1584 /// @todo Update the current task name and push progress.
1585 DD_UpdateEngineStateWorker(&initiatedBusyMode);
1586 }
1587
1588 if (App_GameLoaded() && gx.UpdateState)
1589 {
1590 gx.UpdateState(DD_POST);
1591 }
1592
1593 #ifdef __CLIENT__
1594 world::Materials::get().forAllMaterials([] (world::Material &material)
1595 {
1596 return static_cast<ClientMaterial &>(material).forAllAnimators([] (MaterialAnimator &animator)
1597 {
1598 animator.rewind();
1599 return LoopContinue;
1600 });
1601 });
1602 #endif
1603 }
1604
1605 struct ddvalue_t
1606 {
1607 dint *readPtr;
1608 dint *writePtr;
1609 };
1610
1611 static ddvalue_t ddValues[DD_LAST_VALUE - DD_FIRST_VALUE] = {
1612 {&novideo, 0},
1613 {&netGame, 0},
1614 {&isServer, 0}, // An *open* server?
1615 {&isClient, 0},
1616 {&consolePlayer, &consolePlayer},
1617 {&displayPlayer, 0}, // use R_SetViewPortPlayer() to change
1618 {&gotFrame, 0},
1619 {0, 0}, // pointer updated when queried (DED sound def count)
1620
1621 #ifdef __SERVER__
1622 {&allowFrames, &allowFrames},
1623 #else
1624 {0, 0},
1625 #endif
1626
1627 #ifdef __CLIENT__
1628 {&levelFullBright, &levelFullBright},
1629 {&gameReady, &gameReady},
1630 {&playback, 0},
1631 {&clientPaused, &clientPaused},
1632 {&weaponOffsetScaleY, &weaponOffsetScaleY},
1633 {&gameDrawHUD, 0},
1634 {&symbolicEchoMode, &symbolicEchoMode},
1635 {&rendLightAttenuateFixedColormap, &rendLightAttenuateFixedColormap},
1636 #else
1637 {0, 0},
1638 {0, 0},
1639 {0, 0},
1640 {0, 0},
1641 {0, 0},
1642 {0, 0},
1643 {0, 0},
1644 {0, 0},
1645 #endif
1646 };
1647
1648 #undef DD_GetInteger
1649 /**
1650 * Get a 32-bit signed integer value.
1651 */
DD_GetInteger(dint ddvalue)1652 dint DD_GetInteger(dint ddvalue)
1653 {
1654 switch (ddvalue)
1655 {
1656 #ifdef __CLIENT__
1657 case DD_SHIFT_DOWN:
1658 return dint( ClientApp::inputSystem().shiftDown() );
1659
1660 case DD_WINDOW_WIDTH:
1661 return DENG_GAMEVIEW_WIDTH;
1662
1663 case DD_WINDOW_HEIGHT:
1664 return DENG_GAMEVIEW_HEIGHT;
1665
1666 case DD_CURRENT_CLIENT_FINALE_ID:
1667 return Cl_CurrentFinale();
1668
1669 case DD_DYNLIGHT_TEXTURE:
1670 return dint( GL_PrepareLSTexture(LST_DYNAMIC) );
1671
1672 case DD_USING_HEAD_TRACKING:
1673 return vrCfg().mode() == VRConfig::OculusRift && vrCfg().oculusRift().isReady();
1674 #endif
1675
1676 case DD_NUMMOBJTYPES:
1677 return DED_Definitions()->things.size();
1678
1679 case DD_MAP_MUSIC:
1680 if (App_World().hasMap())
1681 {
1682 Record const &mapInfo = App_World().map().mapInfo();
1683 return DED_Definitions()->getMusicNum(mapInfo.gets("music").toUtf8().constData());
1684 }
1685 return -1;
1686
1687 default: break;
1688 }
1689
1690 if (ddvalue >= DD_LAST_VALUE || ddvalue < DD_FIRST_VALUE)
1691 {
1692 return 0;
1693 }
1694
1695 // Update pointers.
1696 if (ddvalue == DD_NUMSOUNDS)
1697 {
1698 ddValues[ddvalue].readPtr = &DED_Definitions()->sounds.count.num;
1699 }
1700
1701 if (ddValues[ddvalue].readPtr == 0)
1702 {
1703 return 0;
1704 }
1705 return *ddValues[ddvalue].readPtr;
1706 }
1707
1708 #undef DD_SetInteger
1709 /**
1710 * Set a 32-bit signed integer value.
1711 */
DD_SetInteger(dint ddvalue,dint parm)1712 void DD_SetInteger(dint ddvalue, dint parm)
1713 {
1714 if (ddvalue < DD_FIRST_VALUE || ddvalue >= DD_LAST_VALUE)
1715 {
1716 return;
1717 }
1718
1719 if (ddValues[ddvalue].writePtr)
1720 {
1721 *ddValues[ddvalue].writePtr = parm;
1722 }
1723 }
1724
1725 #undef DD_GetVariable
1726 /**
1727 * Get a pointer to the value of a variable. Not all variables support
1728 * this. Added for 64-bit support.
1729 */
DD_GetVariable(dint ddvalue)1730 void *DD_GetVariable(dint ddvalue)
1731 {
1732 static dint value;
1733 static ddouble valueD;
1734 static timespan_t valueT;
1735 static AABoxd valueBox;
1736
1737 switch (ddvalue)
1738 {
1739 case DD_GAME_EXPORTS:
1740 return &gx;
1741
1742 case DD_MAP_POLYOBJ_COUNT:
1743 value = App_World().hasMap()? App_World().map().polyobjCount() : 0;
1744 return &value;
1745
1746 case DD_MAP_BOUNDING_BOX:
1747 if (App_World().hasMap())
1748 {
1749 valueBox = App_World().map().bounds();
1750 }
1751 else
1752 {
1753 valueBox = AABoxd(0.0, 0.0, 0.0, 0.0);
1754 }
1755 return &valueBox;
1756
1757 case DD_MAP_MIN_X:
1758 valueD = App_World().hasMap()? App_World().map().bounds().minX : 0;
1759 return &valueD;
1760
1761 case DD_MAP_MIN_Y:
1762 valueD = App_World().hasMap()? App_World().map().bounds().minY : 0;
1763 return &valueD;
1764
1765 case DD_MAP_MAX_X:
1766 valueD = App_World().hasMap()? App_World().map().bounds().maxX : 0;
1767 return &valueD;
1768
1769 case DD_MAP_MAX_Y:
1770 valueD = App_World().hasMap()? App_World().map().bounds().maxY : 0;
1771 return &valueD;
1772
1773 /*case DD_CPLAYER_THRUST_MUL:
1774 return &cplrThrustMul;*/
1775
1776 case DD_MAP_GRAVITY:
1777 valueD = App_World().hasMap()? App_World().map().gravity() : 0;
1778 return &valueD;
1779
1780 #ifdef __CLIENT__
1781 case DD_PSPRITE_OFFSET_X:
1782 return &pspOffset[0];
1783
1784 case DD_PSPRITE_OFFSET_Y:
1785 return &pspOffset[1];
1786
1787 case DD_PSPRITE_LIGHTLEVEL_MULTIPLIER:
1788 return &pspLightLevelMultiplier;
1789
1790 case DD_TORCH_RED:
1791 return &torchColor.x;
1792
1793 case DD_TORCH_GREEN:
1794 return &torchColor.y;
1795
1796 case DD_TORCH_BLUE:
1797 return &torchColor.z;
1798
1799 //case DD_TORCH_ADDITIVE:
1800 // return &torchAdditive;
1801
1802 # ifdef WIN32
1803 case DD_WINDOW_HANDLE:
1804 return ClientWindow::main().nativeHandle();
1805 # endif
1806 #endif
1807
1808 // We have to separately calculate the 35 Hz ticks.
1809 case DD_GAMETIC:
1810 valueT = gameTime * TICSPERSEC;
1811 return &valueT;
1812
1813 case DD_DEFS:
1814 return DED_Definitions();
1815
1816 default: break;
1817 }
1818
1819 if (ddvalue >= DD_LAST_VALUE || ddvalue < DD_FIRST_VALUE)
1820 {
1821 return 0;
1822 }
1823
1824 // Other values not supported.
1825 return ddValues[ddvalue].writePtr;
1826 }
1827
1828 /**
1829 * Set the value of a variable. The pointer can point to any data, its
1830 * interpretation depends on the variable. Added for 64-bit support.
1831 */
1832 #undef DD_SetVariable
DD_SetVariable(dint ddvalue,void * parm)1833 void DD_SetVariable(dint ddvalue, void *parm)
1834 {
1835 if (ddvalue < DD_FIRST_VALUE || ddvalue >= DD_LAST_VALUE)
1836 {
1837 switch (ddvalue)
1838 {
1839 /*case DD_CPLAYER_THRUST_MUL:
1840 cplrThrustMul = *(dfloat*) parm;
1841 return;*/
1842
1843 case DD_MAP_GRAVITY:
1844 if (App_World().hasMap())
1845 App_World().map().setGravity(*(coord_t*) parm);
1846 return;
1847
1848 #ifdef __CLIENT__
1849 case DD_PSPRITE_OFFSET_X:
1850 pspOffset[0] = *(dfloat *) parm;
1851 return;
1852
1853 case DD_PSPRITE_OFFSET_Y:
1854 pspOffset[1] = *(dfloat *) parm;
1855 return;
1856
1857 case DD_PSPRITE_LIGHTLEVEL_MULTIPLIER:
1858 pspLightLevelMultiplier = *(dfloat *) parm;
1859 return;
1860
1861 case DD_TORCH_RED:
1862 torchColor.x = de::clamp(0.f, *((dfloat*) parm), 1.f);
1863 return;
1864
1865 case DD_TORCH_GREEN:
1866 torchColor.y = de::clamp(0.f, *((dfloat*) parm), 1.f);
1867 return;
1868
1869 case DD_TORCH_BLUE:
1870 torchColor.z = de::clamp(0.f, *((dfloat*) parm), 1.f);
1871 return;
1872
1873 //case DD_TORCH_ADDITIVE:
1874 // torchAdditive = (*(dint*) parm)? true : false;
1875 // break;
1876 #endif
1877
1878 default:
1879 break;
1880 }
1881 }
1882 }
1883
DD_ReadGameHelp()1884 void DD_ReadGameHelp()
1885 {
1886 using namespace de;
1887 LOG_AS("DD_ReadGameHelp");
1888 try
1889 {
1890 if (App_GameLoaded())
1891 {
1892 const de::Uri uri(Path("$(App.DataPath)/$(GamePlugin.Name)/conhelp.txt"));
1893 FS::FoundFiles found;
1894 FS::get().findAll(uri.resolved(), found);
1895 if (found.empty())
1896 {
1897 throw Error("DD_ReadGameHelp", "conhelp.txt not found");
1898 }
1899 Help_ReadStrings(*found.front());
1900 }
1901 }
1902 catch (Error const &er)
1903 {
1904 LOG_RES_WARNING("") << er.asText();
1905 }
1906 }
1907
1908 /// @note Part of the Doomsday public API.
DD_ParseFontSchemeName(char const * str)1909 fontschemeid_t DD_ParseFontSchemeName(char const *str)
1910 {
1911 #ifdef __CLIENT__
1912 try
1913 {
1914 FontScheme &scheme = App_Resources().fontScheme(str);
1915 if (!scheme.name().compareWithoutCase("System"))
1916 {
1917 return FS_SYSTEM;
1918 }
1919 if (!scheme.name().compareWithoutCase("Game"))
1920 {
1921 return FS_GAME;
1922 }
1923 }
1924 catch (Resources::UnknownSchemeError const &)
1925 {}
1926 #endif
1927 qDebug() << "Unknown font scheme:" << String(str) << ", returning 'FS_INVALID'";
1928 return FS_INVALID;
1929 }
1930
DD_MaterialSchemeNameForTextureScheme(String textureSchemeName)1931 String DD_MaterialSchemeNameForTextureScheme(String textureSchemeName)
1932 {
1933 if (!textureSchemeName.compareWithoutCase("Textures"))
1934 {
1935 return "Textures";
1936 }
1937 if (!textureSchemeName.compareWithoutCase("Flats"))
1938 {
1939 return "Flats";
1940 }
1941 if (!textureSchemeName.compareWithoutCase("Sprites"))
1942 {
1943 return "Sprites";
1944 }
1945 if (!textureSchemeName.compareWithoutCase("System"))
1946 {
1947 return "System";
1948 }
1949 return "";
1950 }
1951
DD_MaterialSchemeNameForTextureScheme(ddstring_t const * textureSchemeName)1952 AutoStr *DD_MaterialSchemeNameForTextureScheme(ddstring_t const *textureSchemeName)
1953 {
1954 if (!textureSchemeName)
1955 {
1956 return AutoStr_FromTextStd("");
1957 }
1958 else
1959 {
1960 QByteArray schemeNameUtf8 = DD_MaterialSchemeNameForTextureScheme(String(Str_Text(textureSchemeName))).toUtf8();
1961 return AutoStr_FromTextStd(schemeNameUtf8.constData());
1962 }
1963 }
1964
D_CMD(Load)1965 D_CMD(Load)
1966 {
1967 DENG2_UNUSED(src);
1968 BusyMode_FreezeGameForBusyMode();
1969 auto &loader = PackageLoader::get();
1970
1971 for (int arg = 1; arg < argc; ++arg)
1972 {
1973 String searchTerm = String(argv[arg]).trimmed();
1974 if (!searchTerm) continue;
1975
1976 // Are we loading a game?
1977 if (DoomsdayApp::games().contains(searchTerm))
1978 {
1979 Game const &game = DoomsdayApp::games()[searchTerm];
1980 if (!game.isPlayable())
1981 {
1982 LOG_SCR_ERROR("Game \"%s\" is missing one or more required packages: %s")
1983 << game.id()
1984 << String::join(game.profile().unavailablePackages(), ", ");
1985 return true;
1986 }
1987 if (DoomsdayApp::app().changeGame(game.profile(), DD_ActivateGameWorker))
1988 {
1989 game.profile().setLastPlayedAt();
1990 continue;
1991 }
1992 return false;
1993 }
1994
1995 // It could also be a game profile.
1996 if (auto *profile = DoomsdayApp::gameProfiles().tryFind(searchTerm))
1997 {
1998 auto &gameProf = profile->as<GameProfile>();
1999 if (!gameProf.isPlayable())
2000 {
2001 LOG_SCR_ERROR("Profile \"%s\" is missing one or more required packages: ")
2002 << String::join(gameProf.unavailablePackages(), ", ");
2003 return true;
2004 }
2005 if (DoomsdayApp::app().changeGame(gameProf, DD_ActivateGameWorker))
2006 {
2007 gameProf.setLastPlayedAt();
2008 continue;
2009 }
2010 return false;
2011 }
2012
2013 try
2014 {
2015 // Check packages with a matching name.
2016 if (loader.isAvailable(searchTerm))
2017 {
2018 if (loader.isLoaded(searchTerm))
2019 {
2020 LOG_SCR_MSG("Package \"%s\" is already loaded") << searchTerm;
2021 continue;
2022 }
2023 loader.load(searchTerm);
2024 continue;
2025 }
2026
2027 // Check data bundles with a matching name. We assume the search term
2028 // is a native path.
2029 if (!DoomsdayApp::isGameLoaded())
2030 {
2031 LOG_SCR_ERROR("Cannot load data files when a game isn't loaded");
2032 return false;
2033 }
2034 auto files = DataBundle::findAllNative(searchTerm);
2035 if (files.size() == 1)
2036 {
2037 if (!files.first()->isLinkedAsPackage())
2038 {
2039 LOG_SCR_ERROR("%s cannot be loaded (could be ignored due to "
2040 "being unsupported or invalid") << files.first()->description();
2041 return false;
2042 }
2043 loader.load(files.first()->packageId());
2044 continue;
2045 }
2046 else if (files.size() > 1)
2047 {
2048 LOG_SCR_MSG("There are %i possible matches for the name \"%s\"")
2049 << files.size()
2050 << searchTerm;
2051 if (files.size() <= 10)
2052 {
2053 LOG_SCR_MSG("Maybe you meant:");
2054 for (auto const *f : files)
2055 {
2056 LOG_SCR_MSG("- " _E(>) "%s") << f->description();
2057 }
2058 }
2059 return false;
2060 }
2061 else
2062 {
2063 LOG_SCR_ERROR("No files found matching the name \"%s\"") << searchTerm;
2064 return false;
2065 }
2066 }
2067 catch (Error const &er)
2068 {
2069 LOG_SCR_ERROR("Failed to load package \"%s\": %s") << searchTerm << er.asText();
2070 return false;
2071 }
2072 }
2073 return true;
2074 }
2075
D_CMD(Unload)2076 D_CMD(Unload)
2077 {
2078 DENG2_UNUSED(src);
2079 BusyMode_FreezeGameForBusyMode();
2080
2081 DoomsdayApp &app = DoomsdayApp::app();
2082
2083 try
2084 {
2085 // No arguments; unload the current game if loaded.
2086 if (argc == 1)
2087 {
2088 if (!app.isGameLoaded())
2089 {
2090 LOG_SCR_MSG("No game is currently loaded");
2091 return true;
2092 }
2093 return app.changeGame(GameProfiles::null(), DD_ActivateGameWorker);
2094 }
2095
2096 auto &loader = PackageLoader::get();
2097 auto const loadedPackages = loader.loadedPackages();
2098 auto loadedBundles = DataBundle::loadedBundles();
2099
2100 for (int arg = 1; arg < argc; ++arg)
2101 {
2102 String searchTerm = String(argv[arg]).trimmed();
2103 if (!searchTerm) continue;
2104
2105 if (app.isGameLoaded() && searchTerm == DoomsdayApp::game().id())
2106 {
2107 if (!app.changeGame(GameProfiles::null(), DD_ActivateGameWorker))
2108 {
2109 return false;
2110 }
2111 continue;
2112 }
2113
2114 // Is this one of the loaded packages?
2115 if (loadedPackages.contains(searchTerm) && loader.isAvailable(searchTerm))
2116 {
2117 loader.unload(searchTerm);
2118 continue;
2119 }
2120 for (DataBundle const *bundle : loadedBundles)
2121 {
2122 if (!bundle->sourceFile().name().compareWithoutCase(searchTerm))
2123 {
2124 loadedBundles.removeOne(bundle);
2125 loader.unload(bundle->packageId());
2126 break;
2127 }
2128 }
2129 }
2130 }
2131 catch (Error const &er)
2132 {
2133 LOG_SCR_ERROR("Problem while unloading: %s") << er.asText();
2134 return false;
2135 }
2136
2137 /*AutoStr *searchPath = AutoStr_NewStd();
2138 Str_Set(searchPath, argv[1]);
2139 Str_Strip(searchPath);
2140 if (Str_IsEmpty(searchPath)) return false;
2141
2142 F_FixSlashes(searchPath, searchPath);
2143
2144 // Ignore attempts to unload directories.
2145 if (Str_RAt(searchPath, 0) == '/')
2146 {
2147 LOG_MSG("Directories cannot be \"unloaded\" (only files and/or known games).");
2148 return true;
2149 }
2150
2151 // Unload the current game if specified.
2152 if (argc == 2)
2153 {
2154 try
2155 {
2156 Game &game = App_Games()[Str_Text(searchPath)];
2157 if (App_GameLoaded())
2158 {
2159 return DoomsdayApp::app().changeGame(GameProfiles::null(),
2160 DD_ActivateGameWorker);
2161 }
2162
2163 LOG_MSG("%s is not currently loaded.") << game.id();
2164 return true;
2165 }
2166 catch (Games::NotFoundError const &)
2167 {} // Ignore the error.
2168 }
2169
2170 // Try the resource locator.
2171 bool didUnloadFiles = false;
2172 for (dint i = 1; i < argc; ++i)
2173 {
2174 try
2175 {
2176 String foundPath = App_FileSystem().findPath(de::Uri::fromNativePath(argv[1], RC_PACKAGE),
2177 RLF_MATCH_EXTENSION, App_ResourceClass(RC_PACKAGE));
2178 foundPath = App_BasePath() / foundPath; // Ensure the path is absolute.
2179
2180 if (File1::tryUnload(de::makeUri(foundPath)))
2181 {
2182 didUnloadFiles = true;
2183 }
2184 }
2185 catch (FS1::NotFoundError const&)
2186 {} // Ignore this error.
2187 }
2188
2189 if (didUnloadFiles)
2190 {
2191 // A changed file list may alter the main lump directory.
2192 DD_UpdateEngineState();
2193 }
2194
2195 return didUnloadFiles;*/
2196
2197 return true;
2198 }
2199
D_CMD(Reset)2200 D_CMD(Reset)
2201 {
2202 DENG2_UNUSED3(src, argc, argv);
2203
2204 DD_UpdateEngineState();
2205 return true;
2206 }
2207
D_CMD(ReloadGame)2208 D_CMD(ReloadGame)
2209 {
2210 DENG2_UNUSED3(src, argc, argv);
2211
2212 if (!DoomsdayApp::currentGameProfile())
2213 {
2214 LOG_MSG("No game is presently loaded.");
2215 return true;
2216 }
2217 DoomsdayApp::app().changeGame(*DoomsdayApp::currentGameProfile(),
2218 DD_ActivateGameWorker,
2219 DoomsdayApp::AllowReload);
2220 return true;
2221 }
2222
2223 #if defined (DENG_HAVE_UPDATER)
2224
D_CMD(CheckForUpdates)2225 D_CMD(CheckForUpdates)
2226 {
2227 DENG2_UNUSED3(src, argc, argv);
2228
2229 LOG_MSG("Checking for available updates...");
2230 ClientApp::updater().checkNow(Updater::OnlyShowResultIfUpdateAvailable);
2231 return true;
2232 }
2233
D_CMD(CheckForUpdatesAndNotify)2234 D_CMD(CheckForUpdatesAndNotify)
2235 {
2236 DENG2_UNUSED3(src, argc, argv);
2237
2238 LOG_MSG("Checking for available updates...");
2239 ClientApp::updater().checkNow(Updater::AlwaysShowResult);
2240 return true;
2241 }
2242
D_CMD(LastUpdated)2243 D_CMD(LastUpdated)
2244 {
2245 DENG2_UNUSED3(src, argc, argv);
2246
2247 ClientApp::updater().printLastUpdated();
2248 return true;
2249 }
2250
D_CMD(ShowUpdateSettings)2251 D_CMD(ShowUpdateSettings)
2252 {
2253 DENG2_UNUSED3(src, argc, argv);
2254
2255 ClientApp::updater().showSettings();
2256 return true;
2257 }
2258
2259 #endif // DENG_HAVE_UPDATER
2260
D_CMD(Version)2261 D_CMD(Version)
2262 {
2263 DENG2_UNUSED3(src, argc, argv);
2264
2265 LOG_SCR_NOTE(_E(D) DOOMSDAY_NICENAME " %s") << Version::currentBuild().asHumanReadableText();
2266 LOG_SCR_MSG(_E(l) "Homepage: " _E(.) _E(i) DOOMSDAY_HOMEURL _E(.)
2267 "\n" _E(l) "Project: " _E(.) _E(i) DENGPROJECT_HOMEURL);
2268
2269 // Print the version info of the current game if loaded.
2270 if (App_GameLoaded())
2271 {
2272 LOG_SCR_MSG(_E(l) "Game: " _E(.) "%s") << (char const *) gx.GetPointer(DD_PLUGIN_VERSION_LONG);
2273 }
2274
2275 // Additional information for developers.
2276 Version const ver;
2277 if (!ver.gitDescription.isEmpty())
2278 {
2279 LOGDEV_SCR_MSG(_E(l) "Git revision: " _E(.) "%s") << ver.gitDescription;
2280 }
2281 return true;
2282 }
2283
D_CMD(Quit)2284 D_CMD(Quit)
2285 {
2286 DENG2_UNUSED2(src, argc);
2287
2288 #if defined (DENG_HAVE_UPDATER)
2289 if (UpdateDownloadDialog::isDownloadInProgress())
2290 {
2291 LOG_WARNING("Cannot quit while downloading an update");
2292 ClientWindow::main().taskBar().openAndPauseGame();
2293 UpdateDownloadDialog::currentDownload().open();
2294 return false;
2295 }
2296 #endif
2297
2298 if (argv[0][4] == '!' || isDedicated || !App_GameLoaded() ||
2299 gx.TryShutdown == 0)
2300 {
2301 // No questions asked.
2302 Sys_Quit();
2303 return true; // Never reached.
2304 }
2305
2306 #ifdef __CLIENT__
2307 // Dismiss the taskbar if it happens to be open, we are expecting
2308 // the game to handle this from now on.
2309 ClientWindow::main().taskBar().close();
2310 #endif
2311
2312 // Defer this decision to the loaded game.
2313 return gx.TryShutdown();
2314 }
2315
2316 #ifdef _DEBUG
D_CMD(DebugError)2317 D_CMD(DebugError)
2318 {
2319 DENG2_UNUSED3(src, argv, argc);
2320
2321 App_Error("Fatal error!\n");
2322 return true;
2323 }
2324 #endif
2325
D_CMD(Help)2326 D_CMD(Help)
2327 {
2328 DENG2_UNUSED3(src, argc, argv);
2329
2330 /*
2331 #ifdef __CLIENT__
2332 char actKeyName[40];
2333 strcpy(actKeyName, B_ShortNameForKey(consoleActiveKey));
2334 actKeyName[0] = toupper(actKeyName[0]);
2335 #endif
2336 */
2337
2338 LOG_SCR_NOTE(_E(b) DOOMSDAY_NICENAME " %s Console") << Version::currentBuild().compactNumber();
2339
2340 #define TABBED(A, B) "\n" _E(Ta) _E(b) " " << A << " " _E(.) _E(Tb) << B
2341
2342 #ifdef __CLIENT__
2343 LOG_SCR_MSG(_E(D) "Keys:" _E(.))
2344 << TABBED(DENG2_CHAR_SHIFT_KEY "Esc", "Open the taskbar and console")
2345 << TABBED("Tab", "Autocomplete the word at the cursor")
2346 << TABBED(DENG2_CHAR_UP_DOWN_ARROW, "Move backwards/forwards through the input command history, or up/down one line inside a multi-line command")
2347 << TABBED("PgUp/Dn", "Scroll up/down in the history, or expand the history to full height")
2348 << TABBED(DENG2_CHAR_SHIFT_KEY "PgUp/Dn", "Jump to the top/bottom of the history")
2349 << TABBED("Home", "Move the cursor to the start of the command line")
2350 << TABBED("End", "Move the cursor to the end of the command line")
2351 << TABBED(DENG2_CHAR_CONTROL_KEY "K", "Clear everything on the line right of the cursor position")
2352 << TABBED("F5", "Clear the console message history");
2353 #endif
2354 LOG_SCR_MSG(_E(D) "Getting started:");
2355 LOG_SCR_MSG(" " _E(>) "Enter " _E(b) "help (what)" _E(.) " for information about " _E(l) "(what)");
2356 LOG_SCR_MSG(" " _E(>) "Enter " _E(b) "listcmds" _E(.) " to list available commands");
2357 LOG_SCR_MSG(" " _E(>) "Enter " _E(b) "listgames" _E(.) " to list installed games and their status");
2358 LOG_SCR_MSG(" " _E(>) "Enter " _E(b) "listvars" _E(.) " to list available variables");
2359
2360 #undef TABBED
2361
2362 return true;
2363 }
2364
printHelpAbout(char const * query)2365 static void printHelpAbout(char const *query)
2366 {
2367 // Try the console commands first.
2368 if (ccmd_t *ccmd = Con_FindCommand(query))
2369 {
2370 LOG_SCR_MSG(_E(b) "%s" _E(.) " (Command)") << ccmd->name;
2371
2372 HelpId help = DH_Find(ccmd->name);
2373 if (char const *description = DH_GetString(help, HST_DESCRIPTION))
2374 {
2375 LOG_SCR_MSG("") << description;
2376 }
2377
2378 Con_PrintCommandUsage(ccmd); // For all overloaded variants.
2379
2380 // Any extra info?
2381 if (char const *info = DH_GetString(help, HST_INFO))
2382 {
2383 LOG_SCR_MSG(" " _E(>) _E(l)) << info;
2384 }
2385 return;
2386 }
2387
2388 if (cvar_t *var = Con_FindVariable(query))
2389 {
2390 AutoStr *path = CVar_ComposePath(var);
2391 LOG_SCR_MSG(_E(b) "%s" _E(.) " (Variable)") << Str_Text(path);
2392
2393 HelpId help = DH_Find(Str_Text(path));
2394 if (char const *description = DH_GetString(help, HST_DESCRIPTION))
2395 {
2396 LOG_SCR_MSG("") << description;
2397 }
2398 return;
2399 }
2400
2401 if (calias_t *calias = Con_FindAlias(query))
2402 {
2403 LOG_SCR_MSG(_E(b) "%s" _E(.) " alias of:\n")
2404 << calias->name << calias->command;
2405 return;
2406 }
2407
2408 // Perhaps a game?
2409 try
2410 {
2411 Game &game = App_Games()[query];
2412 LOG_SCR_MSG(_E(b) "%s" _E(.) " (IdentityKey)") << game.id();
2413
2414 LOG_SCR_MSG("Unique identifier of the " _E(b) "%s" _E(.) " game mode.") << game.title();
2415 LOG_SCR_MSG("An 'IdentityKey' is used when referencing a game unambiguously from the console and on the command line.");
2416 LOG_SCR_MSG(_E(D) "Related commands:");
2417 LOG_SCR_MSG(" " _E(>) "Enter " _E(b) "inspectgame %s" _E(.) " for information and status of this game") << game.id();
2418 LOG_SCR_MSG(" " _E(>) "Enter " _E(b) "listgames" _E(.) " to list all installed games and their status");
2419 LOG_SCR_MSG(" " _E(>) "Enter " _E(b) "load %s" _E(.) " to load the " _E(l) "%s" _E(.) " game mode") << game.id() << game.title();
2420 return;
2421 }
2422 catch (Games::NotFoundError const &)
2423 {} // Ignore this error.
2424
2425 LOG_SCR_NOTE("There is no help about '%s'") << query;
2426 }
2427
D_CMD(HelpWhat)2428 D_CMD(HelpWhat)
2429 {
2430 DENG2_UNUSED2(argc, src);
2431
2432 if (!String(argv[1]).compareWithoutCase("(what)"))
2433 {
2434 LOG_SCR_MSG("You've got to be kidding!");
2435 return true;
2436 }
2437
2438 printHelpAbout(argv[1]);
2439 return true;
2440 }
2441
2442 #ifdef __CLIENT__
D_CMD(Clear)2443 D_CMD(Clear)
2444 {
2445 DENG2_UNUSED3(src, argc, argv);
2446
2447 ClientWindow::main().console().clearLog();
2448 return true;
2449 }
2450 #endif
2451
DD_ConsoleRegister()2452 void DD_ConsoleRegister()
2453 {
2454 C_VAR_CHARPTR("file-startup", &startupFiles, 0, 0, 0);
2455
2456 C_CMD("help", "", Help);
2457 C_CMD("help", "s", HelpWhat);
2458 C_CMD("version", "", Version);
2459 C_CMD("quit", "", Quit);
2460 C_CMD("quit!", "", Quit);
2461 C_CMD("load", "s*", Load);
2462 C_CMD("reset", "", Reset);
2463 C_CMD("reload", "", ReloadGame);
2464 C_CMD("unload", "*", Unload);
2465 C_CMD("write", "s", WriteConsole);
2466
2467 #ifdef DENG2_DEBUG
2468 C_CMD("fatalerror", nullptr, DebugError);
2469 #endif
2470
2471 DD_RegisterLoop();
2472 Def_ConsoleRegister();
2473 FS1::consoleRegister();
2474 Con_Register();
2475 Games::consoleRegister();
2476 DH_Register();
2477 AudioSystem::consoleRegister();
2478
2479 #ifdef __CLIENT__
2480 C_CMD("clear", "", Clear);
2481
2482 #if defined (DENG_HAVE_UPDATER)
2483 C_CMD("update", "", CheckForUpdates);
2484 C_CMD("updateandnotify", "", CheckForUpdatesAndNotify);
2485 C_CMD("updatesettings", "", ShowUpdateSettings);
2486 C_CMD("lastupdated", "", LastUpdated);
2487 #endif
2488
2489 C_CMD_FLAGS("conclose", "", OpenClose, CMDF_NO_DEDICATED);
2490 C_CMD_FLAGS("conopen", "", OpenClose, CMDF_NO_DEDICATED);
2491 C_CMD_FLAGS("contoggle", "", OpenClose, CMDF_NO_DEDICATED);
2492 C_CMD ("taskbar", "", TaskBar);
2493 C_CMD ("tutorial", "", Tutorial);
2494 C_CMD ("packages", "", PackagesSidebar);
2495
2496 /// @todo Move to UI module.
2497 Con_TransitionRegister();
2498
2499 InputSystem::consoleRegister();
2500 #if 0
2501 SBE_Register(); // for bias editor
2502 #endif
2503 RenderSystem::consoleRegister();
2504 GL_Register();
2505 //UI_Register();
2506 Demo_Register();
2507 P_ConsoleRegister();
2508 I_Register();
2509 ClientResources::consoleRegister();
2510 #endif
2511
2512 #ifdef __SERVER__
2513 Resources::consoleRegister();
2514 #endif
2515
2516 Net_Register();
2517 ClientServerWorld::consoleRegister();
2518 InFineSystem::consoleRegister();
2519 }
2520
2521 // dd_loop.c
2522 DENG_EXTERN_C dd_bool DD_IsSharpTick(void);
2523
2524 // net_main.c
2525 DENG_EXTERN_C void Net_SendPacket(dint to_player, dint type, const void* data, size_t length);
2526
2527 #undef R_SetupMap
R_SetupMap(dint mode,dint flags)2528 DENG_EXTERN_C void R_SetupMap(dint mode, dint flags)
2529 {
2530 DENG2_UNUSED2(mode, flags);
2531
2532 if (!App_World().hasMap()) return; // Huh?
2533
2534 // Perform map setup again. Its possible that after loading we now
2535 // have more HOMs to fix, etc..
2536 world::Map &map = App_World().map();
2537
2538 #ifdef __CLIENT__
2539 map.initSkyFix();
2540 #endif
2541
2542 #ifdef __CLIENT__
2543 /*
2544 // Update all sectors.
2545 /// @todo Refactor away.
2546 map.forAllSectors([] (Sector §or)
2547 {
2548 return sector.forAllSubsectors([] (world::Subsector &subsec)
2549 {
2550 return subsec.as<world::ClientSubsector>().forAllEdgeLoops([] (world::ClEdgeLoop &loop)
2551 {
2552 loop.fixSurfacesMissingMaterials();
2553 return LoopContinue;
2554 });
2555 });
2556 });
2557 */
2558 #endif
2559
2560 // Re-initialize polyobjs.
2561 /// @todo Still necessary?
2562 map.initPolyobjs();
2563
2564 // Reset the timer so that it will appear that no time has passed.
2565 DD_ResetTimer();
2566 }
2567
2568 // sys_system.c
2569 DENG_EXTERN_C void Sys_Quit();
2570
2571 DENG_DECLARE_API(Base) =
2572 {
2573 { DE_API_BASE },
2574
2575 Sys_Quit,
2576 DD_GetInteger,
2577 DD_SetInteger,
2578 DD_GetVariable,
2579 DD_SetVariable,
2580 DD_GameInfo,
2581 DD_IsSharpTick,
2582 Net_SendPacket,
2583 R_SetupMap
2584 };
2585