1 //
2 // Copyright(C) 1993-1996 Id Software, Inc.
3 // Copyright(C) 1993-2008 Raven Software
4 // Copyright(C) 2005-2014 Simon Howard
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
10 //
11 // This program 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 
17 // D_main.c
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 
22 #include "txt_main.h"
23 #include "txt_io.h"
24 
25 #include "net_client.h"
26 
27 #include "config.h"
28 #include "ct_chat.h"
29 #include "doomdef.h"
30 #include "deh_main.h"
31 #include "d_iwad.h"
32 #include "i_endoom.h"
33 #include "i_input.h"
34 #include "i_joystick.h"
35 #include "i_sound.h"
36 #include "i_swap.h" // [crispy] SHORT()
37 #include "i_system.h"
38 #include "i_timer.h"
39 #include "i_video.h"
40 #include "m_argv.h"
41 #include "m_config.h"
42 #include "m_controls.h"
43 #include "m_misc.h"
44 #include "p_local.h"
45 #include "s_sound.h"
46 #include "w_main.h"
47 #include "v_video.h"
48 #include "v_trans.h" // [crispy] dp_translation
49 
50 #define CT_KEY_GREEN    'g'
51 #define CT_KEY_YELLOW   'y'
52 #define CT_KEY_RED      'r'
53 #define CT_KEY_BLUE     'b'
54 
55 #define STARTUP_WINDOW_X 17
56 #define STARTUP_WINDOW_Y 7
57 
58 GameMode_t gamemode = indetermined;
59 const char *gamedescription = "unknown";
60 
61 boolean nomonsters;             // checkparm of -nomonsters
62 boolean respawnparm;            // checkparm of -respawn
63 boolean debugmode;              // checkparm of -debug
64 boolean ravpic;                 // checkparm of -ravpic
65 boolean cdrom;                  // true if cd-rom mode active
66 boolean noartiskip;             // whether shift-enter skips an artifact
67 
68 skill_t startskill;
69 int startepisode;
70 int startmap;
71 int UpdateState;
72 static int graphical_startup = 0;
73 static boolean using_graphical_startup;
74 static boolean main_loop_started = false;
75 boolean autostart;
76 extern boolean automapactive;
77 
78 boolean advancedemo;
79 
80 FILE *debugfile;
81 
82 static int show_endoom = 0;
83 
84 void D_ConnectNetGame(void);
85 void D_CheckNetGame(void);
86 void D_PageDrawer(void);
87 void D_AdvanceDemo(void);
88 boolean F_Responder(event_t * ev);
89 
90 //---------------------------------------------------------------------------
91 //
92 // PROC D_ProcessEvents
93 //
94 // Send all the events of the given timestamp down the responder chain.
95 //
96 //---------------------------------------------------------------------------
97 
D_ProcessEvents(void)98 void D_ProcessEvents(void)
99 {
100     event_t *ev;
101 
102     while ((ev = D_PopEvent()) != NULL)
103     {
104         if (F_Responder(ev))
105         {
106             continue;
107         }
108         if (MN_Responder(ev))
109         {
110             continue;
111         }
112         G_Responder(ev);
113     }
114 }
115 
116 //---------------------------------------------------------------------------
117 //
118 // PROC DrawMessage
119 //
120 //---------------------------------------------------------------------------
121 
DrawMessage(void)122 void DrawMessage(void)
123 {
124     player_t *player;
125 
126     player = &players[consoleplayer];
127     if (player->messageTics <= 0 || !player->message)
128     {                           // No message
129         return;
130     }
131     MN_DrTextA(player->message, 160 - MN_TextAWidth(player->message) / 2, 1);
132 }
133 
134 //---------------------------------------------------------------------------
135 //
136 // PROC DrawCenterMessage
137 //
138 // [crispy]
139 //
140 //---------------------------------------------------------------------------
141 
DrawCenterMessage(void)142 void DrawCenterMessage(void)
143 {
144     player_t* player;
145 
146     player = &players[consoleplayer];
147     if (player->centerMessageTics <= 0 || !player->centerMessage)
148     {                           // No message
149         return;
150     }
151     // Place message above quit game message position so they don't overlap
152     dp_translation = cr[CR_GOLD];
153     MN_DrTextA(player->centerMessage, 160 - MN_TextAWidth(player->centerMessage) / 2, 70);
154     dp_translation = NULL;
155 }
156 
157 //---------------------------------------------------------------------------
158 //
159 // PROC D_Display
160 //
161 // Draw current display, possibly wiping it from the previous.
162 //
163 //---------------------------------------------------------------------------
164 
CrispyDrawStats(void)165 static void CrispyDrawStats (void)
166 {
167     static short height, coord_x;
168     char str[32];
169     player_t *const player = &players[consoleplayer];
170 
171     if (!height || !coord_x)
172     {
173 	const int FontABaseLump = W_GetNumForName(DEH_String("FONTA_S")) + 1;
174 	const patch_t *const p = W_CacheLumpNum(FontABaseLump + 'A' - 33, PU_CACHE);
175 
176 	height = SHORT(p->height) + 1;
177 	coord_x = ORIGWIDTH - 7 * SHORT(p->width);
178     }
179 
180     if (crispy->automapstats == WIDGETS_ALWAYS || (automapactive && crispy->automapstats == WIDGETS_AUTOMAP))
181     {
182 	M_snprintf(str, sizeof(str), "K %d/%d", player->killcount, totalkills);
183 	MN_DrTextA(str, 0, 1*height);
184 
185 	M_snprintf(str, sizeof(str), "I %d/%d", player->itemcount, totalitems);
186 	MN_DrTextA(str, 0, 2*height);
187 
188 	M_snprintf(str, sizeof(str), "S %d/%d", player->secretcount, totalsecret);
189 	MN_DrTextA(str, 0, 3*height);
190     }
191 
192     if (crispy->leveltime == WIDGETS_ALWAYS || (automapactive && crispy->leveltime == WIDGETS_AUTOMAP))
193     {
194 	const int time = leveltime / TICRATE;
195 
196 	M_snprintf(str, sizeof(str), "%02d:%02d", time/60, time%60);
197 	MN_DrTextA(str, 0, 4*height);
198     }
199 
200     if (crispy->playercoords == WIDGETS_ALWAYS || (automapactive && crispy->playercoords == WIDGETS_AUTOMAP))
201     {
202 	M_snprintf(str, sizeof(str), "X %-5d", player->mo->x>>FRACBITS);
203 	MN_DrTextA(str, coord_x, 1*height);
204 
205 	M_snprintf(str, sizeof(str), "Y %-5d", player->mo->y>>FRACBITS);
206 	MN_DrTextA(str, coord_x, 2*height);
207 
208 	M_snprintf(str, sizeof(str), "A %-5d", player->mo->angle/ANG1);
209 	MN_DrTextA(str, coord_x, 3*height);
210     }
211 }
212 
213 // [crispy] Draw the current FPS if show fps cheat is active
CrispyDrawFps(void)214 static void CrispyDrawFps(void)
215 {
216     short coord_x, height;
217     char str[32];
218     player_t* const player = &players[consoleplayer];
219 
220     const int FontABaseLump = W_GetNumForName(DEH_String("FONTA_S")) + 1;
221     const patch_t* const p = W_CacheLumpNum(FontABaseLump + 'A' - 33, PU_CACHE);
222 
223     height = SHORT(p->height) + 1;
224     coord_x = ORIGWIDTH - 6 * SHORT(p->width);
225 
226     // [crispy] Only show FPS outside automap as it could obscure level coords
227     if (player->cheats & CF_SHOWFPS && !automapactive)
228     {
229         M_snprintf(str, sizeof(str), "%d F", crispy->fps);
230         MN_DrTextA(str, coord_x, height);
231     }
232 }
233 
234 void R_ExecuteSetViewSize(void);
235 
236 extern boolean finalestage;
237 
D_Display(void)238 void D_Display(void)
239 {
240     extern boolean askforquit;
241 
242     // Change the view size if needed
243     if (setsizeneeded)
244     {
245         R_ExecuteSetViewSize();
246     }
247 
248 //
249 // do buffered drawing
250 //
251     switch (gamestate)
252     {
253         case GS_LEVEL:
254             if (!gametic)
255                 break;
256             if (automapactive && !crispy->automapoverlay)
257             {
258                 // [crispy] update automap while playing
259                 R_RenderPlayerView (&players[displayplayer]);
260                 AM_Drawer();
261             }
262             else
263                 R_RenderPlayerView(&players[displayplayer]);
264             if (automapactive && crispy->automapoverlay)
265             {
266                 AM_Drawer();
267                 BorderNeedRefresh = true;
268             }
269             CT_Drawer();
270             UpdateState |= I_FULLVIEW;
271             SB_Drawer();
272             CrispyDrawStats();
273             CrispyDrawFps();
274             break;
275         case GS_INTERMISSION:
276             IN_Drawer();
277             break;
278         case GS_FINALE:
279             F_Drawer();
280             break;
281         case GS_DEMOSCREEN:
282             D_PageDrawer();
283             break;
284     }
285 
286     if (testcontrols)
287     {
288         V_DrawMouseSpeedBox(testcontrols_mousespeed);
289     }
290 
291     if (paused && !MenuActive && !askforquit)
292     {
293         if (!netgame)
294         {
295             V_DrawPatch(160, (viewwindowy >> crispy->hires) + 5, W_CacheLumpName(DEH_String("PAUSED"),
296                                                               PU_CACHE));
297         }
298         else
299         {
300             V_DrawPatch(160, 70, W_CacheLumpName(DEH_String("PAUSED"), PU_CACHE));
301         }
302     }
303     // Handle player messages
304     DrawMessage();
305 
306     // [crispy] Handle centered player messages
307     DrawCenterMessage();
308 
309     // Menu drawing
310     MN_Drawer();
311 
312     // Send out any new accumulation
313     NetUpdate();
314 
315     // Flush buffered stuff to screen
316     I_FinishUpdate();
317 }
318 
319 //
320 // D_GrabMouseCallback
321 //
322 // Called to determine whether to grab the mouse pointer
323 //
324 
D_GrabMouseCallback(void)325 boolean D_GrabMouseCallback(void)
326 {
327     // when menu is active or game is paused, release the mouse
328 
329     if (MenuActive || paused)
330         return false;
331 
332     // only grab mouse when playing levels (but not demos)
333 
334     return (gamestate == GS_LEVEL) && !demoplayback && !advancedemo;
335 }
336 
337 //---------------------------------------------------------------------------
338 //
339 // PROC D_DoomLoop
340 //
341 //---------------------------------------------------------------------------
342 
D_DoomLoop(void)343 void D_DoomLoop(void)
344 {
345     if (M_CheckParm("-debugfile"))
346     {
347         char filename[20];
348         M_snprintf(filename, sizeof(filename), "debug%i.txt", consoleplayer);
349         debugfile = fopen(filename, "w");
350     }
351     I_GraphicsCheckCommandLine();
352     I_SetGrabMouseCallback(D_GrabMouseCallback);
353     I_InitGraphics();
354 
355     main_loop_started = true;
356 
357     while (1)
358     {
359         // Frame syncronous IO operations
360         I_StartFrame();
361 
362         // Process one or more tics
363         // Will run at least one tic
364         TryRunTics();
365 
366         // Move positional sounds
367         S_UpdateSounds(players[consoleplayer].mo);
368         D_Display();
369 
370         // [crispy] post-rendering function pointer to apply config changes
371         // that affect rendering and that are better applied after the current
372         // frame has finished rendering
373         if (crispy->post_rendering_hook)
374         {
375             crispy->post_rendering_hook();
376             crispy->post_rendering_hook = NULL;
377         }
378     }
379 }
380 
381 /*
382 ===============================================================================
383 
384 						DEMO LOOP
385 
386 ===============================================================================
387 */
388 
389 static int demosequence;
390 static int pagetic;
391 static const char *pagename;
392 
393 
394 /*
395 ================
396 =
397 = D_PageTicker
398 =
399 = Handles timing for warped projection
400 =
401 ================
402 */
403 
D_PageTicker(void)404 void D_PageTicker(void)
405 {
406     if (--pagetic < 0)
407         D_AdvanceDemo();
408 }
409 
410 
411 /*
412 ================
413 =
414 = D_PageDrawer
415 =
416 ================
417 */
418 
D_PageDrawer(void)419 void D_PageDrawer(void)
420 {
421     V_DrawRawScreen(W_CacheLumpName(pagename, PU_CACHE));
422     if (demosequence == 1)
423     {
424         V_DrawPatch(4, 160, W_CacheLumpName(DEH_String("ADVISOR"), PU_CACHE));
425     }
426     UpdateState |= I_FULLSCRN;
427 }
428 
429 /*
430 =================
431 =
432 = D_AdvanceDemo
433 =
434 = Called after each demo or intro demosequence finishes
435 =================
436 */
437 
D_AdvanceDemo(void)438 void D_AdvanceDemo(void)
439 {
440     advancedemo = true;
441 }
442 
D_DoAdvanceDemo(void)443 void D_DoAdvanceDemo(void)
444 {
445     players[consoleplayer].playerstate = PST_LIVE;      // don't reborn
446     advancedemo = false;
447     usergame = false;           // can't save / end game here
448     paused = false;
449     gameaction = ga_nothing;
450     demosequence = (demosequence + 1) % 7;
451     switch (demosequence)
452     {
453         case 0:
454             pagetic = 210;
455             gamestate = GS_DEMOSCREEN;
456             pagename = DEH_String("TITLE");
457             S_StartSong(mus_titl, false);
458             break;
459         case 1:
460             pagetic = 140;
461             gamestate = GS_DEMOSCREEN;
462             pagename = DEH_String("TITLE");
463             break;
464         case 2:
465             BorderNeedRefresh = true;
466             UpdateState |= I_FULLSCRN;
467             G_DeferedPlayDemo(DEH_String("demo1"));
468             break;
469         case 3:
470             pagetic = 200;
471             gamestate = GS_DEMOSCREEN;
472             pagename = DEH_String("CREDIT");
473             break;
474         case 4:
475             BorderNeedRefresh = true;
476             UpdateState |= I_FULLSCRN;
477             G_DeferedPlayDemo(DEH_String("demo2"));
478             break;
479         case 5:
480             pagetic = 200;
481             gamestate = GS_DEMOSCREEN;
482             if (gamemode == shareware)
483             {
484                 pagename = DEH_String("ORDER");
485             }
486             else
487             {
488                 pagename = DEH_String("CREDIT");
489             }
490             break;
491         case 6:
492             BorderNeedRefresh = true;
493             UpdateState |= I_FULLSCRN;
494             G_DeferedPlayDemo(DEH_String("demo3"));
495             break;
496     }
497 }
498 
499 
500 /*
501 =================
502 =
503 = D_StartTitle
504 =
505 =================
506 */
507 
D_StartTitle(void)508 void D_StartTitle(void)
509 {
510     gameaction = ga_nothing;
511     demosequence = -1;
512     D_AdvanceDemo();
513 }
514 
515 
516 /*
517 ==============
518 =
519 = D_CheckRecordFrom
520 =
521 = -recordfrom <savegame num> <demoname>
522 ==============
523 */
524 
D_CheckRecordFrom(void)525 void D_CheckRecordFrom(void)
526 {
527     int p;
528     char *filename;
529 
530     //!
531     // @vanilla
532     // @category demo
533     // @arg <savenum> <demofile>
534     //
535     // Record a demo, loading from the given filename. Equivalent
536     // to -loadgame <savenum> -record <demofile>.
537 
538     p = M_CheckParmWithArgs("-recordfrom", 2);
539     if (!p)
540         return;
541 
542     filename = SV_Filename(myargv[p + 1][0] - '0');
543     G_LoadGame(filename);
544     G_DoLoadGame();             // load the gameskill etc info from savegame
545 
546     G_RecordDemo(gameskill, 1, gameepisode, gamemap, myargv[p + 2]);
547     D_DoomLoop();               // never returns
548     free(filename);
549 }
550 
551 /*
552 ===============
553 =
554 = D_AddFile
555 =
556 ===============
557 */
558 
559 // MAPDIR should be defined as the directory that holds development maps
560 // for the -wart # # command
561 
562 #define MAPDIR "\\data\\"
563 
564 #define SHAREWAREWADNAME "heretic1.wad"
565 
566 char *iwadfile;
567 
568 
wadprintf(void)569 void wadprintf(void)
570 {
571     if (debugmode)
572     {
573         return;
574     }
575 }
576 
D_AddFile(char * file)577 boolean D_AddFile(char *file)
578 {
579     wad_file_t *handle;
580 
581     printf("  adding %s\n", file);
582 
583     handle = W_AddFile(file);
584 
585     return handle != NULL;
586 }
587 
588 //==========================================================
589 //
590 //  Startup Thermo code
591 //
592 //==========================================================
593 #define MSG_Y       9
594 #define THERM_X     14
595 #define THERM_Y     14
596 
597 int thermMax;
598 int thermCurrent;
599 char smsg[80];                  // status bar line
600 
601 //
602 //  Heretic startup screen shit
603 //
604 
605 static int startup_line = STARTUP_WINDOW_Y;
606 
hprintf(const char * string)607 void hprintf(const char *string)
608 {
609     if (using_graphical_startup)
610     {
611         TXT_BGColor(TXT_COLOR_CYAN, 0);
612         TXT_FGColor(TXT_COLOR_BRIGHT_WHITE);
613 
614         TXT_GotoXY(STARTUP_WINDOW_X, startup_line);
615         ++startup_line;
616         TXT_Puts(string);
617 
618         TXT_UpdateScreen();
619     }
620 
621     // haleyjd: shouldn't be WATCOMC-only
622     if (debugmode)
623         puts(string);
624 }
625 
drawstatus(void)626 void drawstatus(void)
627 {
628     int i;
629 
630     TXT_GotoXY(1, 24);
631     TXT_BGColor(TXT_COLOR_BLUE, 0);
632     TXT_FGColor(TXT_COLOR_BRIGHT_WHITE);
633 
634     for (i=0; smsg[i] != '\0'; ++i)
635     {
636         TXT_PutChar(smsg[i]);
637     }
638 }
639 
status(const char * string)640 static void status(const char *string)
641 {
642     if (using_graphical_startup)
643     {
644         M_StringConcat(smsg, string, sizeof(smsg));
645         drawstatus();
646     }
647 }
648 
DrawThermo(void)649 void DrawThermo(void)
650 {
651     static int last_progress = -1;
652     int progress;
653     int i;
654 
655     if (!using_graphical_startup)
656     {
657         return;
658     }
659 
660     // No progress? Don't update the screen.
661 
662     progress = (50 * thermCurrent) / thermMax + 2;
663 
664     if (last_progress == progress)
665     {
666         return;
667     }
668 
669     last_progress = progress;
670 
671     TXT_GotoXY(THERM_X, THERM_Y);
672 
673     TXT_FGColor(TXT_COLOR_BRIGHT_GREEN);
674     TXT_BGColor(TXT_COLOR_GREEN, 0);
675 
676     for (i = 0; i < progress; i++)
677     {
678         TXT_PutChar(0xdb);
679     }
680 
681     TXT_UpdateScreen();
682 }
683 
initStartup(void)684 void initStartup(void)
685 {
686     byte *textScreen;
687     byte *loading;
688 
689     if (!graphical_startup || debugmode || testcontrols)
690     {
691         using_graphical_startup = false;
692         return;
693     }
694 
695     if (!TXT_Init())
696     {
697         using_graphical_startup = false;
698         return;
699     }
700 
701     I_InitWindowTitle();
702     I_InitWindowIcon();
703 
704     // Blit main screen
705     textScreen = TXT_GetScreenData();
706     loading = W_CacheLumpName(DEH_String("LOADING"), PU_CACHE);
707     memcpy(textScreen, loading, 4000);
708 
709     // Print version string
710 
711     TXT_BGColor(TXT_COLOR_RED, 0);
712     TXT_FGColor(TXT_COLOR_YELLOW);
713     TXT_GotoXY(46, 2);
714     TXT_Puts(HERETIC_VERSION_TEXT);
715 
716     TXT_UpdateScreen();
717 
718     using_graphical_startup = true;
719 }
720 
finishStartup(void)721 static void finishStartup(void)
722 {
723     if (using_graphical_startup)
724     {
725         TXT_Shutdown();
726     }
727 }
728 
729 char tmsg[300];
tprintf(const char * msg,int initflag)730 void tprintf(const char *msg, int initflag)
731 {
732     printf("%s", msg);
733 }
734 
735 // haleyjd: moved up, removed WATCOMC code
CleanExit(void)736 void CleanExit(void)
737 {
738     DEH_printf("Exited from HERETIC.\n");
739     exit(1);
740 }
741 
CheckAbortStartup(void)742 void CheckAbortStartup(void)
743 {
744     // haleyjd: removed WATCOMC
745     // haleyjd FIXME: this should actually work in text mode too, but how to
746     // get input before SDL video init?
747     if(using_graphical_startup)
748     {
749         if(TXT_GetChar() == 27)
750             CleanExit();
751     }
752 }
753 
IncThermo(void)754 void IncThermo(void)
755 {
756     thermCurrent++;
757     DrawThermo();
758     CheckAbortStartup();
759 }
760 
InitThermo(int max)761 void InitThermo(int max)
762 {
763     thermMax = max;
764     thermCurrent = 0;
765 }
766 
767 //
768 // Add configuration file variable bindings.
769 //
770 
D_BindVariables(void)771 void D_BindVariables(void)
772 {
773     extern int screenblocks;
774     extern int snd_Channels;
775     int i;
776 
777     M_ApplyPlatformDefaults();
778 
779     I_BindInputVariables();
780     I_BindVideoVariables();
781     I_BindJoystickVariables();
782     I_BindSoundVariables();
783 
784     M_BindBaseControls();
785     M_BindHereticControls();
786     M_BindWeaponControls();
787     M_BindChatControls(MAXPLAYERS);
788 
789     key_multi_msgplayer[0] = CT_KEY_GREEN;
790     key_multi_msgplayer[1] = CT_KEY_YELLOW;
791     key_multi_msgplayer[2] = CT_KEY_RED;
792     key_multi_msgplayer[3] = CT_KEY_BLUE;
793 
794     M_BindMenuControls();
795     M_BindMapControls();
796 
797     NET_BindVariables();
798 
799     M_BindIntVariable("mouse_sensitivity",      &mouseSensitivity);
800     M_BindIntVariable("mouse_sensitivity_x2",   &mouseSensitivity_x2);
801     M_BindIntVariable("mouse_sensitivity_y",    &mouseSensitivity_y);
802     M_BindIntVariable("sfx_volume",             &snd_MaxVolume);
803     M_BindIntVariable("music_volume",           &snd_MusicVolume);
804     M_BindIntVariable("screenblocks",           &screenblocks);
805     M_BindIntVariable("snd_channels",           &snd_Channels);
806     M_BindIntVariable("vanilla_savegame_limit", &vanilla_savegame_limit);
807     M_BindIntVariable("vanilla_demo_limit",     &vanilla_demo_limit);
808     M_BindIntVariable("show_endoom",            &show_endoom);
809     M_BindIntVariable("graphical_startup",      &graphical_startup);
810 
811     for (i=0; i<10; ++i)
812     {
813         char buf[12];
814 
815         M_snprintf(buf, sizeof(buf), "chatmacro%i", i);
816         M_BindStringVariable(buf, &chat_macros[i]);
817     }
818 
819     // [crispy] bind "crispness" config variables
820     M_BindIntVariable("crispy_hires",           &crispy->hires);
821     M_BindIntVariable("crispy_smoothscaling",   &crispy->smoothscaling);
822     M_BindIntVariable("crispy_automapstats",    &crispy->automapstats);
823     M_BindIntVariable("crispy_leveltime",       &crispy->leveltime);
824     M_BindIntVariable("crispy_playercoords",    &crispy->playercoords);
825     M_BindIntVariable("crispy_secretmessage",   &crispy->secretmessage);
826     M_BindIntVariable("crispy_uncapped",        &crispy->uncapped);
827     M_BindIntVariable("crispy_vsync",           &crispy->vsync);
828 }
829 
830 //
831 // Called at exit to display the ENDOOM screen (ENDTEXT in Heretic)
832 //
833 
D_Endoom(void)834 static void D_Endoom(void)
835 {
836     byte *endoom_data;
837 
838     // Disable ENDOOM?
839 
840     if (!show_endoom || testcontrols || !main_loop_started)
841     {
842         return;
843     }
844 
845     endoom_data = W_CacheLumpName(DEH_String("ENDTEXT"), PU_STATIC);
846 
847     I_Endoom(endoom_data);
848 }
849 
850 //---------------------------------------------------------------------------
851 //
852 // PROC D_DoomMain
853 //
854 //---------------------------------------------------------------------------
855 
D_DoomMain(void)856 void D_DoomMain(void)
857 {
858     GameMission_t gamemission;
859     int p;
860     char file[256];
861     char demolumpname[9];
862 
863     I_PrintBanner(PACKAGE_STRING);
864 
865     I_AtExit(D_Endoom, false);
866 
867     //!
868     // @category game
869     // @vanilla
870     //
871     // Disable monsters.
872     //
873 
874     nomonsters = M_ParmExists("-nomonsters");
875 
876     //!
877     // @category game
878     // @vanilla
879     //
880     // Monsters respawn after being killed.
881     //
882 
883     respawnparm = M_ParmExists("-respawn");
884 
885     //!
886     // @vanilla
887     //
888     // Take screenshots when F1 is pressed.
889     //
890 
891     ravpic = M_ParmExists("-ravpic");
892 
893     //!
894     // @category obscure
895     // @vanilla
896     //
897     // Allow artifacts to be used when the run key is held down.
898     //
899 
900     noartiskip = M_ParmExists("-noartiskip");
901 
902     debugmode = M_ParmExists("-debug");
903     startskill = sk_medium;
904     startepisode = 1;
905     startmap = 1;
906     autostart = false;
907 
908 //
909 // get skill / episode / map from parms
910 //
911 
912     //!
913     // @vanilla
914     // @category net
915     //
916     // Start a deathmatch game.
917     //
918 
919     if (M_ParmExists("-deathmatch"))
920     {
921         deathmatch = true;
922     }
923 
924     //!
925     // @category game
926     // @arg <skill>
927     // @vanilla
928     //
929     // Set the game skill, 1-5 (1: easiest, 5: hardest).  A skill of
930     // 0 disables all monsters.
931     //
932 
933     p = M_CheckParmWithArgs("-skill", 1);
934     if (p)
935     {
936         startskill = myargv[p + 1][0] - '1';
937         autostart = true;
938     }
939 
940     //!
941     // @category game
942     // @arg <n>
943     // @vanilla
944     //
945     // Start playing on episode n (1-4)
946     //
947 
948     p = M_CheckParmWithArgs("-episode", 1);
949     if (p)
950     {
951         startepisode = myargv[p + 1][0] - '0';
952         startmap = 1;
953         autostart = true;
954     }
955 
956     //!
957     // @category game
958     // @arg <x> <y>
959     // @vanilla
960     //
961     // Start a game immediately, warping to level ExMy.
962     //
963 
964     p = M_CheckParmWithArgs("-warp", 2);
965     if (p && p < myargc - 2)
966     {
967         startepisode = myargv[p + 1][0] - '0';
968         startmap = myargv[p + 2][0] - '0';
969         autostart = true;
970 
971         // [crispy] if used with -playdemo, fast-forward demo up to the desired map
972         crispy->demowarp = startmap;
973     }
974 
975 //
976 // init subsystems
977 //
978     DEH_printf("V_Init: allocate screens.\n");
979     V_Init();
980 
981     // Check for -CDROM
982 
983     cdrom = false;
984 
985 #ifdef _WIN32
986 
987     //!
988     // @category obscure
989     // @platform windows
990     // @vanilla
991     //
992     // Save configuration data and savegames in c:\heretic.cd,
993     // allowing play from CD.
994     //
995 
996     if (M_CheckParm("-cdrom"))
997     {
998         cdrom = true;
999     }
1000 #endif
1001 
1002     if (cdrom)
1003     {
1004         M_SetConfigDir(DEH_String("c:\\heretic.cd"));
1005     }
1006     else
1007     {
1008         M_SetConfigDir(NULL);
1009     }
1010 
1011     // Load defaults before initing other systems
1012     DEH_printf("M_LoadDefaults: Load system defaults.\n");
1013     D_BindVariables();
1014     M_SetConfigFilenames("heretic.cfg", PROGRAM_PREFIX "heretic.cfg");
1015     M_LoadDefaults();
1016 
1017     I_AtExit(M_SaveDefaults, false);
1018 
1019     DEH_printf("Z_Init: Init zone memory allocation daemon.\n");
1020     Z_Init();
1021 
1022     DEH_printf("W_Init: Init WADfiles.\n");
1023 
1024     iwadfile = D_FindIWAD(IWAD_MASK_HERETIC, &gamemission);
1025 
1026     if (iwadfile == NULL)
1027     {
1028         I_Error("Game mode indeterminate. No IWAD was found. Try specifying\n"
1029                 "one with the '-iwad' command line parameter.");
1030     }
1031 
1032     D_AddFile(iwadfile);
1033     W_CheckCorrectIWAD(heretic);
1034 
1035     //!
1036     // @category game
1037     // @category mod
1038     //
1039     // Automatic wand start when advancing from one level to the next. At the
1040     // beginning of each level, the player's health is reset to 100, their
1041     // armor to 0 and their inventory is reduced to the following: wand, staff
1042     // and 50 ammo for the wand. This option is not allowed when recording a
1043     // demo, playing back a demo or when starting a network game.
1044     //
1045 
1046     crispy->pistolstart = M_ParmExists("-wandstart");
1047 
1048     //!
1049     // @category mod
1050     //
1051     // Disable auto-loading of .wad files.
1052     //
1053     if (!M_ParmExists("-noautoload"))
1054     {
1055         char *autoload_dir;
1056         autoload_dir = M_GetAutoloadDir("heretic.wad", true);
1057         DEH_AutoLoadPatches(autoload_dir);
1058         W_AutoLoadWADs(autoload_dir);
1059         free(autoload_dir);
1060     }
1061 
1062     // Load dehacked patches specified on the command line.
1063     DEH_ParseCommandLine();
1064 
1065     // Load PWAD files.
1066     W_ParseCommandLine();
1067 
1068     //!
1069     // @arg <demo>
1070     // @category demo
1071     // @vanilla
1072     //
1073     // Play back the demo named demo.lmp.
1074     //
1075 
1076     p = M_CheckParmWithArgs("-playdemo", 1);
1077     if (!p)
1078     {
1079         //!
1080         // @arg <demo>
1081         // @category demo
1082         // @vanilla
1083         //
1084         // Play back the demo named demo.lmp, determining the framerate
1085         // of the screen.
1086         //
1087 
1088         p = M_CheckParmWithArgs("-timedemo", 1);
1089     }
1090 
1091     if (p)
1092     {
1093         char *uc_filename = strdup(myargv[p + 1]);
1094         M_ForceUppercase(uc_filename);
1095 
1096         // In Vanilla, the filename must be specified without .lmp,
1097         // but make that optional.
1098         if (M_StringEndsWith(uc_filename, ".LMP"))
1099         {
1100             M_StringCopy(file, myargv[p + 1], sizeof(file));
1101         }
1102         else
1103         {
1104             DEH_snprintf(file, sizeof(file), "%s.lmp", myargv[p + 1]);
1105         }
1106 
1107         free(uc_filename);
1108 
1109         if (D_AddFile(file))
1110         {
1111             M_StringCopy(demolumpname, lumpinfo[numlumps - 1]->name,
1112                          sizeof(demolumpname));
1113         }
1114         else
1115         {
1116             // The file failed to load, but copy the original arg as a
1117             // demo name to make tricks like -playdemo demo1 possible.
1118             M_StringCopy(demolumpname, myargv[p + 1], sizeof(demolumpname));
1119         }
1120 
1121         printf("Playing demo %s.\n", file);
1122     }
1123 
1124     // Generate the WAD hash table.  Speed things up a bit.
1125     W_GenerateHashTable();
1126 
1127     //!
1128     // @category demo
1129     //
1130     // Record or playback a demo, automatically quitting
1131     // after either level exit or player respawn.
1132     //
1133 
1134     demoextend = (!M_ParmExists("-nodemoextend"));
1135     //[crispy] make demoextend the default
1136 
1137     if (W_CheckNumForName(DEH_String("E2M1")) == -1)
1138     {
1139         gamemode = shareware;
1140         gamedescription = "Heretic (shareware)";
1141     }
1142     else if (W_CheckNumForName("EXTENDED") != -1)
1143     {
1144         // Presence of the EXTENDED lump indicates the retail version
1145 
1146         gamemode = retail;
1147         gamedescription = "Heretic: Shadow of the Serpent Riders";
1148     }
1149     else
1150     {
1151         gamemode = registered;
1152         gamedescription = "Heretic (registered)";
1153     }
1154 
1155     I_SetWindowTitle(gamedescription);
1156 
1157     savegamedir = M_GetSaveGameDir("heretic.wad");
1158 
1159     I_PrintStartupBanner(gamedescription);
1160 
1161     if (M_ParmExists("-testcontrols"))
1162     {
1163         startepisode = 1;
1164         startmap = 1;
1165         autostart = true;
1166         testcontrols = true;
1167     }
1168 
1169     I_InitTimer();
1170     I_InitSound(false);
1171     I_InitMusic();
1172 
1173     tprintf("NET_Init: Init network subsystem.\n", 1);
1174     NET_Init ();
1175 
1176     D_ConnectNetGame();
1177 
1178     // haleyjd: removed WATCOMC
1179     initStartup();
1180 
1181     //
1182     //  Build status bar line!
1183     //
1184     smsg[0] = 0;
1185     if (deathmatch)
1186         status(DEH_String("DeathMatch..."));
1187     if (nomonsters)
1188         status(DEH_String("No Monsters..."));
1189     if (respawnparm)
1190         status(DEH_String("Respawning..."));
1191     if (autostart)
1192     {
1193         char temp[64];
1194         DEH_snprintf(temp, sizeof(temp),
1195                      "Warp to Episode %d, Map %d, Skill %d ",
1196                      startepisode, startmap, startskill + 1);
1197         status(temp);
1198     }
1199     wadprintf();                // print the added wadfiles
1200 
1201     tprintf(DEH_String("MN_Init: Init menu system.\n"), 1);
1202     MN_Init();
1203 
1204     CT_Init();
1205 
1206     tprintf(DEH_String("R_Init: Init Heretic refresh daemon."), 1);
1207     hprintf(DEH_String("Loading graphics"));
1208     R_Init();
1209     tprintf("\n", 0);
1210 
1211     tprintf(DEH_String("P_Init: Init Playloop state.\n"), 1);
1212     hprintf(DEH_String("Init game engine."));
1213     P_Init();
1214     IncThermo();
1215 
1216     tprintf(DEH_String("I_Init: Setting up machine state.\n"), 1);
1217     I_CheckIsScreensaver();
1218     I_InitJoystick();
1219     IncThermo();
1220 
1221     tprintf(DEH_String("S_Init: Setting up sound.\n"), 1);
1222     S_Init();
1223     //IO_StartupTimer();
1224     S_Start();
1225 
1226     tprintf(DEH_String("D_CheckNetGame: Checking network game status.\n"), 1);
1227     hprintf(DEH_String("Checking network game status."));
1228     D_CheckNetGame();
1229     IncThermo();
1230 
1231     // haleyjd: removed WATCOMC
1232 
1233     tprintf(DEH_String("SB_Init: Loading patches.\n"), 1);
1234     SB_Init();
1235     IncThermo();
1236 
1237 //
1238 // start the appropriate game based on params
1239 //
1240 
1241     D_CheckRecordFrom();
1242 
1243     //!
1244     // @arg <x>
1245     // @category demo
1246     // @vanilla
1247     //
1248     // Record a demo named x.lmp.
1249     //
1250 
1251     p = M_CheckParmWithArgs("-record", 1);
1252     if (p)
1253     {
1254         G_RecordDemo(startskill, 1, startepisode, startmap, myargv[p + 1]);
1255         D_DoomLoop();           // Never returns
1256     }
1257 
1258     p = M_CheckParmWithArgs("-playdemo", 1);
1259     if (p)
1260     {
1261         singledemo = true;      // Quit after one demo
1262         G_DeferedPlayDemo(demolumpname);
1263         D_DoomLoop();           // Never returns
1264     }
1265 
1266     // [crispy] we don't play a demo, so don't skip maps
1267     crispy->demowarp = 0;
1268 
1269     p = M_CheckParmWithArgs("-timedemo", 1);
1270     if (p)
1271     {
1272         G_TimeDemo(demolumpname);
1273         D_DoomLoop();           // Never returns
1274     }
1275 
1276     //!
1277     // @category game
1278     // @arg <s>
1279     // @vanilla
1280     //
1281     // Load the game in savegame slot s.
1282     //
1283 
1284     p = M_CheckParmWithArgs("-loadgame", 1);
1285     if (p && p < myargc - 1)
1286     {
1287         char *filename;
1288 
1289 	filename = SV_Filename(myargv[p + 1][0] - '0');
1290         G_LoadGame(filename);
1291 	free(filename);
1292     }
1293 
1294     // Check valid episode and map
1295     if (autostart || netgame)
1296     {
1297         if (!D_ValidEpisodeMap(heretic, gamemode, startepisode, startmap))
1298         {
1299             startepisode = 1;
1300             startmap = 1;
1301         }
1302     }
1303 
1304     if (gameaction != ga_loadgame)
1305     {
1306         UpdateState |= I_FULLSCRN;
1307         BorderNeedRefresh = true;
1308         if (autostart || netgame)
1309         {
1310             G_InitNew(startskill, startepisode, startmap);
1311         }
1312         else
1313         {
1314             D_StartTitle();
1315         }
1316     }
1317 
1318     finishStartup();
1319 
1320     D_DoomLoop();               // Never returns
1321 }
1322