1 //---------------------------------------------------------------------------
2 // EDGE Main Init + Program Loop Code
3 //----------------------------------------------------------------------------
4 //
5 // Copyright (c) 1999-2009 The EDGE Team.
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
16 //
17 //----------------------------------------------------------------------------
18 //
19 // Based on the DOOM source code, released by Id Software under the
20 // following copyright:
21 //
22 // Copyright (C) 1993-1996 by id Software, Inc.
23 //
24 //----------------------------------------------------------------------------
25 //
26 // DESCRIPTION:
27 // EDGE main program (E_Main),
28 // game loop (E_Loop) and startup functions.
29 //
30 // -MH- 1998/07/02 "shootupdown" --> "true3dgameplay"
31 // -MH- 1998/08/19 added up/down movement variables
32 //
33
34 #include "i_defs.h"
35 #include "e_main.h"
36
37 #include <sys/stat.h>
38 #include <sys/types.h>
39 #include <time.h>
40
41 #include "epi/exe_path.h"
42 #include "epi/file.h"
43 #include "epi/filesystem.h"
44 #include "epi/path.h"
45 #include "epi/utility.h"
46
47 #include "am_map.h"
48 #include "con_gui.h"
49 #include "con_main.h"
50 #include "con_var.h"
51 #include "dm_defs.h"
52 #include "dm_state.h"
53 #include "dstrings.h"
54 #include "e_input.h"
55 #include "f_finale.h"
56 #include "f_interm.h"
57 #include "g_game.h"
58 #include "hu_draw.h"
59 #include "hu_stuff.h"
60 #include "l_glbsp.h"
61 #include "m_argv.h"
62 #include "m_bbox.h"
63 #include "m_cheat.h"
64 #include "m_misc.h"
65 #include "m_menu.h"
66 #include "n_network.h"
67 #include "p_setup.h"
68 #include "p_spec.h"
69 #include "r_local.h"
70 #include "rad_trig.h"
71 #include "r_gldefs.h"
72 #include "r_wipe.h"
73 #include "s_sound.h"
74 #include "s_music.h"
75 #include "sv_chunk.h"
76 #include "sv_main.h"
77 #include "r_colormap.h"
78 #include "r_draw.h"
79 #include "r_modes.h"
80 #include "r_image.h"
81 #include "w_model.h"
82 #include "w_sprite.h"
83 #include "w_texture.h"
84 #include "w_wad.h"
85 #include "version.h"
86 #include "vm_coal.h"
87 #include "z_zone.h"
88
89
90 #define E_TITLE "EDGE v" EDGEVERSTR
91
92 // Application active?
93 int app_state = APP_STATE_ACTIVE;
94
95 bool singletics = false; // debug flag to cancel adaptiveness
96
97 // -ES- 2000/02/13 Takes screenshot every screenshot_rate tics.
98 // Must be used in conjunction with singletics.
99 static int screenshot_rate;
100
101 // For screenies...
102 bool m_screenshot_required = false;
103 bool need_save_screenshot = false;
104
105 FILE *logfile = NULL;
106 FILE *debugfile = NULL;
107
108 gameflags_t default_gameflags =
109 {
110 false, // nomonsters
111 false, // fastparm
112
113 false, // respawn
114 false, // res_respawn
115 false, // item respawn
116
117 false, // true 3d gameplay
118 MENU_GRAV_NORMAL, // gravity
119 false, // more blood
120
121 true, // jump
122 true, // crouch
123 true, // mlook
124 AA_ON, // autoaim
125
126 true, // cheats
127 true, // have_extra
128 false, // limit_zoom
129 false, // shadows
130 false, // halos
131
132 false, // edge_compat
133 true, // kicking
134 true, // weapon_switch
135 true, // pass_missile
136 false, // team_damage
137 };
138
139 // -KM- 1998/12/16 These flags are the users prefs and are copied to
140 // gameflags when a new level is started.
141 // -AJA- 2000/02/02: Removed initialisation (done in code using
142 // `default_gameflags').
143
144 gameflags_t global_flags;
145
146 int newnmrespawn = 0;
147
148 bool swapstereo = false;
149 bool mus_pause_stop = false;
150 bool png_scrshots = false;
151 bool autoquickload = false;
152
153 std::string cfgfile;
154 std::string ewadfile;
155 std::string iwad_base;
156
157 std::string cache_dir;
158 std::string ddf_dir;
159 std::string game_dir;
160 std::string home_dir;
161 std::string save_dir;
162 std::string shot_dir;
163
164 extern cvar_c m_language;
165 extern cvar_c g_aggression;
166
167 cvar_c ddf_strict;
168 cvar_c ddf_lax;
169 cvar_c ddf_quiet;
170
171 static void E_TitleDrawer(void);
172
173
174 class startup_progress_c
175 {
176 private:
177 int perc;
178 int g_step, g_size, g_total; // global progress
179 int l_step, l_total; // local progress
180
181 public:
startup_progress_c()182 startup_progress_c() :
183 perc(-1), // force update on initial setting
184 g_step(0), g_size(0), g_total(0),
185 l_step(0), l_total(0) { }
186
~startup_progress_c()187 ~startup_progress_c() { }
188
recomputePercent()189 void recomputePercent()
190 {
191 int pp = (100 * g_step + (100 * g_size * l_step / l_total)) / g_total;
192
193 SYS_ASSERT(0 <= pp && pp <= 100);
194
195 if (pp != perc)
196 {
197 perc = pp; drawIt();
198 }
199 }
200
drawIt(int glbsp_perc=-1)201 void drawIt(int glbsp_perc = -1) // a bit kludgy...
202 {
203 RGL_DrawProgress(perc, glbsp_perc);
204 }
205
setGlobal(int step,int size,int total)206 void setGlobal(int step, int size, int total)
207 {
208 g_step = step;
209 g_size = size;
210 g_total = total;
211 }
212
setLocal(int step,int total)213 void setLocal(int step, int total)
214 {
215 l_step = step;
216 l_total = total;
217 }
218 };
219
220 static startup_progress_c s_progress;
221
E_ProgressMessage(const char * message)222 void E_ProgressMessage(const char *message)
223 {
224 // FIXME: show message near progress bar
225 I_Printf("%s", message);
226 }
227
E_LocalProgress(int step,int total)228 void E_LocalProgress(int step, int total)
229 {
230 s_progress.setLocal(step, total);
231 s_progress.recomputePercent();
232 }
233
E_GlobalProgress(int step,int size,int total)234 void E_GlobalProgress(int step, int size, int total)
235 {
236 s_progress.setGlobal(step, size, total);
237
238 E_LocalProgress(0, 1); // recomputes the percentage
239 }
240
E_NodeMessage(const char * message)241 void E_NodeMessage(const char *message)
242 {
243 // FIXME: show message
244 }
245
E_NodeProgress(int perc)246 void E_NodeProgress(int perc)
247 {
248 s_progress.drawIt(perc);
249 }
250
251 //
252 // -ACB- 1999/09/20 Created. Sets Global Stuff.
253 //
SetGlobalVars(void)254 static void SetGlobalVars(void)
255 {
256 int p;
257 const char *s;
258
259 // Screen Resolution Check...
260 s = M_GetParm("-width");
261 if (s)
262 SCREENWIDTH = atoi(s);
263
264 s = M_GetParm("-height");
265 if (s)
266 SCREENHEIGHT = atoi(s);
267
268 p = M_CheckParm("-res");
269 if (p && p + 2 < M_GetArgCount())
270 {
271 SCREENWIDTH = atoi(M_GetArgument(p + 1));
272 SCREENHEIGHT = atoi(M_GetArgument(p + 2));
273 }
274
275 // Bits per pixel check....
276 s = M_GetParm("-bpp");
277 if (s)
278 {
279 SCREENBITS = atoi(s);
280
281 if (SCREENBITS <= 4) // backwards compat
282 SCREENBITS *= 8;
283 }
284
285 // restrict depth to allowable values
286 if (SCREENBITS < 15) SCREENBITS = 15;
287 else if (SCREENBITS > 32) SCREENBITS = 32;
288
289 M_CheckBooleanParm("windowed", &FULLSCREEN, true);
290 M_CheckBooleanParm("fullscreen", &FULLSCREEN, false);
291
292 // sprite kludge (TrueBSP)
293 p = M_CheckParm("-spritekludge");
294 if (p)
295 {
296 if (p + 1 < M_GetArgCount())
297 sprite_kludge = atoi(M_GetArgument(p + 1));
298
299 if (!sprite_kludge)
300 sprite_kludge = 1;
301 }
302
303 s = M_GetParm("-screenshot");
304 if (s)
305 {
306 screenshot_rate = atoi(s);
307 singletics = true;
308 }
309
310 // -AJA- 1999/10/18: Reworked these with M_CheckBooleanParm
311 M_CheckBooleanParm("rotatemap", &rotatemap, false);
312 M_CheckBooleanParm("sound", &nosound, true);
313 M_CheckBooleanParm("music", &nomusic, true);
314 M_CheckBooleanParm("cdmusic", &nocdmusic, true);
315 M_CheckBooleanParm("itemrespawn", &global_flags.itemrespawn, false);
316 M_CheckBooleanParm("mlook", &global_flags.mlook, false);
317 M_CheckBooleanParm("monsters", &global_flags.nomonsters, true);
318 M_CheckBooleanParm("fast", &global_flags.fastparm, false);
319 M_CheckBooleanParm("extras", &global_flags.have_extra, false);
320 M_CheckBooleanParm("shadows", &global_flags.shadows, false);
321 M_CheckBooleanParm("halos", &global_flags.halos, false);
322 M_CheckBooleanParm("kick", &global_flags.kicking, false);
323 M_CheckBooleanParm("singletics", &singletics, false);
324 M_CheckBooleanParm("true3d", &global_flags.true3dgameplay, false);
325 M_CheckBooleanParm("blood", &global_flags.more_blood, false);
326 M_CheckBooleanParm("cheats", &global_flags.cheats, false);
327 M_CheckBooleanParm("jumping", &global_flags.jump, false);
328 M_CheckBooleanParm("crouching", &global_flags.crouch, false);
329 M_CheckBooleanParm("weaponswitch", &global_flags.weapon_switch, false);
330 M_CheckBooleanParm("autoload", &autoquickload, false);
331
332 if (M_CheckParm("-infight"))
333 g_aggression = 1;
334
335 if (M_CheckParm("-dlights"))
336 use_dlights = 1;
337 else if (M_CheckParm("-nodlights"))
338 use_dlights = 0;
339
340 if (M_CheckParm("-ecompat"))
341 global_flags.edge_compat = true;
342
343 if (!global_flags.respawn)
344 {
345 if (M_CheckParm("-newnmrespawn"))
346 {
347 global_flags.res_respawn = true;
348 global_flags.respawn = true;
349 }
350 else if (M_CheckParm("-respawn"))
351 {
352 global_flags.respawn = true;
353 }
354 }
355
356 // check for strict and no-warning options
357 M_CheckBooleanCVar("strict", &ddf_strict, false);
358 M_CheckBooleanCVar("lax", &ddf_lax, false);
359 M_CheckBooleanCVar("warn", &ddf_quiet, true);
360
361 strict_errors = ddf_strict.d ? true : false;
362 lax_errors = ddf_lax.d ? true : false;
363 no_warnings = ddf_quiet.d ? true : false;
364 }
365
366 //
367 // SetLanguage
368 //
SetLanguage(void)369 void SetLanguage(void)
370 {
371 const char *want_lang = M_GetParm("-lang");
372 if (! want_lang)
373 want_lang = M_GetParm("-language");
374
375 if (want_lang)
376 m_language = want_lang;
377
378 if (language.Select(m_language.str))
379 return;
380
381 I_Warning("Invalid language: '%s'\n", m_language.str);
382
383 if (! language.IsValid())
384 if (! language.Select(0))
385 I_Error("Unable to select any language!");
386
387 m_language = language.GetName();
388 }
389
390 //
391 // SpecialWadVerify
392 //
SpecialWadVerify(void)393 static void SpecialWadVerify(void)
394 {
395 int lump = W_CheckNumForName("EDGEVER");
396 if (lump < 0)
397 I_Error("EDGEVER lump not found. Get EDGE.WAD at http://edge.sourceforge.net/");
398
399 const void *data = W_CacheLumpNum(lump);
400
401 // parse version number
402 const char *s = (const char*)data;
403 int wad_ver = atoi(s) * 100;
404
405 while (isdigit(*s)) s++;
406 s++;
407 wad_ver += atoi(s);
408
409 W_DoneWithLump(data);
410
411 I_Printf("EDGE.WAD version %1.2f found.\n", wad_ver / 100.0);
412
413 if (wad_ver < EDGE_WAD_VERSION)
414 {
415 I_Warning("EDGE.WAD is an older version (expected %1.2f)\n",
416 EDGE_WAD_VERSION / 100.0);
417 }
418 else if (wad_ver > EDGE_WAD_VERSION)
419 {
420 I_Warning("EDGE.WAD is a newer version (expected %1.2f)\n",
421 EDGE_WAD_VERSION / 100.0);
422 }
423 }
424
425 //
426 // ShowNotice
427 //
ShowNotice(void)428 static void ShowNotice(void)
429 {
430 CON_MessageColor(RGB_MAKE(64,192,255));
431
432 I_Printf("%s", language["Notice"]);
433 }
434
435
DoSystemStartup(void)436 static void DoSystemStartup(void)
437 {
438 // startup the system now
439 W_InitImages();
440
441 I_Debugf("- System startup begun.\n");
442
443 I_SystemStartup();
444
445 // -ES- 1998/09/11 Use R_ChangeResolution to enter gfx mode
446
447 R_DumpResList();
448
449 // -KM- 1998/09/27 Change res now, so music doesn't start before
450 // screen. Reset clock too.
451 I_Debugf("- Changing Resolution...\n");
452
453 R_InitialResolution();
454
455 RGL_Init();
456 R_SoftInitResolution();
457
458 I_Debugf("- System startup done.\n");
459 }
460
461
M_DisplayPause(void)462 static void M_DisplayPause(void)
463 {
464 static const image_c *pause_image = NULL;
465
466 if (! pause_image)
467 pause_image = W_ImageLookup("M_PAUSE");
468
469 // make sure image is centered horizontally
470
471 float w = IM_WIDTH(pause_image);
472 float h = IM_HEIGHT(pause_image);
473
474 float x = 160 - w / 2;
475 float y = 10;
476
477 HUD_StretchImage(x, y, w, h, pause_image);
478 }
479
480
481 wipetype_e wipe_method = WIPE_Melt;
482 int wipe_reverse = 0;
483
484 static bool need_wipe = false;
485
E_ForceWipe(void)486 void E_ForceWipe(void)
487 {
488 if (gamestate == GS_NOTHING)
489 return;
490
491 if (wipe_method == WIPE_None)
492 return;
493
494 need_wipe = true;
495
496 // capture screen now (before new level is loaded etc..)
497 E_Display();
498 }
499
500 //
501 // E_Display
502 //
503 // Draw current display, possibly wiping it from the previous
504 //
505 // -ACB- 1998/07/27 Removed doublebufferflag check (unneeded).
506
507 static bool wipe_gl_active = false;
508
E_Display(void)509 void E_Display(void)
510 {
511 if (nodrawers)
512 return; // for comparative timing / profiling
513
514 // Start the frame - should we need to.
515 I_StartFrame();
516
517 HUD_FrameSetup();
518
519 // -AJA- 1999/08/02: Make sure palette/gamma is OK. This also should
520 // fix (finally !) the "gamma too late on walls" bug.
521 V_ColourNewFrame();
522
523 switch (gamestate)
524 {
525 case GS_LEVEL:
526 HU_Erase();
527
528 R_PaletteStuff();
529
530 VM_RunHud();
531
532 if (need_save_screenshot)
533 {
534 M_MakeSaveScreenShot();
535 need_save_screenshot = false;
536 }
537
538 HU_Drawer();
539 RAD_Drawer();
540 break;
541
542 case GS_INTERMISSION:
543 WI_Drawer();
544 break;
545
546 case GS_FINALE:
547 F_Drawer();
548 break;
549
550 case GS_TITLESCREEN:
551 E_TitleDrawer();
552 break;
553
554 case GS_NOTHING:
555 break;
556 }
557
558 if (wipe_gl_active)
559 {
560 // -AJA- Wipe code for GL. Sorry for all this ugliness, but it just
561 // didn't fit into the existing wipe framework.
562 //
563 if (RGL_DoWipe())
564 {
565 RGL_StopWipe();
566 wipe_gl_active = false;
567 }
568 }
569
570 // save the current screen if about to wipe
571 if (need_wipe)
572 {
573 need_wipe = false;
574 wipe_gl_active = true;
575
576 RGL_InitWipe(wipe_reverse, wipe_method);
577 }
578
579 if (paused)
580 M_DisplayPause();
581
582 // menus go directly to the screen
583 M_Drawer(); // menu is drawn even on top of everything (except console)
584
585 N_NetUpdate(); // send out any new accumulation
586
587 if (m_screenshot_required)
588 {
589 m_screenshot_required = false;
590 M_ScreenShot(true);
591 }
592 else if (screenshot_rate && (gamestate >= GS_LEVEL))
593 {
594 SYS_ASSERT(singletics);
595
596 if (leveltime % screenshot_rate == 0)
597 M_ScreenShot(false);
598 }
599
600 // draw console _after_ doing screenshots
601 CON_Drawer();
602
603 M_DisplayDisk();
604
605 I_FinishFrame(); // page flip or blit buffer
606 }
607
608
609 //
610 // DEMO LOOP
611 //
612 static int title_game;
613 static int title_pic;
614 static int title_countdown;
615
616 static const image_c *title_image = NULL;
617
618
E_TitleDrawer(void)619 static void E_TitleDrawer(void)
620 {
621 if (title_image)
622 HUD_StretchImage(0, 0, 320, 200, title_image);
623 else
624 HUD_SolidBox(0, 0, 320, 200, RGB_MAKE(64,64,64));
625 }
626
627
628 //
629 // This cycles through the demo sequences.
630 // -KM- 1998/12/16 Fixed for DDF.
631 //
E_AdvanceTitle(void)632 void E_AdvanceTitle(void)
633 {
634 title_pic++;
635
636 // prevent an infinite loop
637 for (int loop=0; loop < 100; loop++)
638 {
639 gamedef_c *g = gamedefs[title_game];
640 SYS_ASSERT(g);
641
642 if (title_pic >= g->titlepics.GetSize())
643 {
644 title_game = (title_game + 1) % gamedefs.GetSize();
645 title_pic = 0;
646 continue;
647 }
648
649 // ignore non-existing episodes. Doesn't include title-only ones
650 // like [EDGE].
651 if (title_pic == 0 && g->firstmap && g->firstmap[0] &&
652 W_CheckNumForName(g->firstmap) == -1)
653 {
654 title_game = (title_game + 1) % gamedefs.GetSize();
655 title_pic = 0;
656 continue;
657 }
658
659 // ignore non-existing images
660 title_image = W_ImageLookup(g->titlepics[title_pic], INS_Graphic, ILF_Null);
661
662 if (! title_image)
663 {
664 title_pic++;
665 continue;
666 }
667
668 // found one !!
669
670 if (title_pic == 0 && g->titlemusic > 0)
671 S_ChangeMusic(g->titlemusic, false);
672
673 title_countdown = g->titletics;
674 return;
675 }
676
677 // not found
678
679 title_image = NULL;
680 title_countdown = TICRATE;
681 }
682
683
E_StartTitle(void)684 void E_StartTitle(void)
685 {
686 gameaction = ga_nothing;
687 gamestate = GS_TITLESCREEN;
688
689 paused = false;
690
691 // force pic overflow -> first available titlepic
692 title_game = gamedefs.GetSize() - 1;
693 title_pic = 29999;
694 title_countdown = 1;
695
696 E_AdvanceTitle();
697 }
698
699
E_TitleTicker(void)700 void E_TitleTicker(void)
701 {
702 if (title_countdown > 0)
703 {
704 title_countdown--;
705
706 if (title_countdown == 0)
707 E_AdvanceTitle();
708 }
709 }
710
711
712 //
713 // Detects which directories to search for DDFs, WADs and other files in.
714 //
715 // -ES- 2000/01/01 Written.
716 //
InitDirectories(void)717 void InitDirectories(void)
718 {
719 std::string path;
720
721 const char *s = M_GetParm("-home");
722 if (s)
723 home_dir = s;
724
725 // Get the Home Directory from environment if set
726 if (home_dir.empty())
727 {
728 s = getenv("HOME");
729 if (s)
730 {
731 home_dir = epi::PATH_Join(s, EDGEHOMESUBDIR);
732
733 if (! epi::FS_IsDir(home_dir.c_str()))
734 {
735 epi::FS_MakeDir(home_dir.c_str());
736
737 // Check whether the directory was created
738 if (! epi::FS_IsDir(home_dir.c_str()))
739 home_dir.clear();
740 }
741 }
742 }
743
744 if (home_dir.empty())
745 home_dir = "."; // Default to current directory
746
747 // Get the Game Directory from parameter.
748 s = epi::GetResourcePath();
749 game_dir = s;
750 free((void*)s);
751
752 s = M_GetParm("-game");
753 if (s)
754 game_dir = s;
755
756 // add parameter file "gamedir/parms" if it exists.
757 std::string parms = epi::PATH_Join(game_dir.c_str(), "parms");
758
759 if (epi::FS_Access(parms.c_str(), epi::file_c::ACCESS_READ))
760 {
761 // Insert it right after the game parameter
762 M_ApplyResponseFile(parms.c_str(), M_CheckParm("-game") + 2);
763 }
764
765 s = M_GetParm("-ddf");
766 if (s)
767 {
768 ddf_dir = std::string(s);
769 }
770 else
771 {
772 ddf_dir = epi::PATH_Join(game_dir.c_str(), "doom_ddf");
773 }
774
775 DDF_SetWhere(ddf_dir);
776
777 // config file
778 s = M_GetParm("-config");
779 if (s)
780 {
781 cfgfile = M_ComposeFileName(home_dir.c_str(), s);
782 }
783 else
784 {
785 cfgfile = epi::PATH_Join(home_dir.c_str(), EDGECONFIGFILE);
786 }
787
788 // edge.wad file
789 s = M_GetParm("-ewad");
790 if (s)
791 {
792 ewadfile = M_ComposeFileName(home_dir.c_str(), s);
793 }
794 else
795 {
796 ewadfile = epi::PATH_Join(home_dir.c_str(), "edge.wad");
797 }
798
799 // cache directory
800 cache_dir = epi::PATH_Join(home_dir.c_str(), CACHEDIR);
801
802 if (! epi::FS_IsDir(cache_dir.c_str()))
803 epi::FS_MakeDir(cache_dir.c_str());
804
805 // savegame directory
806 save_dir = epi::PATH_Join(home_dir.c_str(), SAVEGAMEDIR);
807
808 if (! epi::FS_IsDir(save_dir.c_str()))
809 epi::FS_MakeDir(save_dir.c_str());
810
811 SV_ClearSlot("current");
812
813 // screenshot directory
814 shot_dir = epi::PATH_Join(home_dir.c_str(), SCRNSHOTDIR);
815
816 if (!epi::FS_IsDir(shot_dir.c_str()))
817 epi::FS_MakeDir(shot_dir.c_str());
818 }
819
820
821 //
822 // Adds an IWAD and EDGE.WAD. -ES- 2000/01/01 Rewritten.
823 //
824 const char *wadname[] = { "doom2", "doom", "plutonia", "tnt", "hacx", "freedoom", "freedm", NULL };
825
IdentifyVersion(void)826 static void IdentifyVersion(void)
827 {
828 I_Debugf("- Identify Version\n");
829
830 // Check -iwad parameter, find out if it is the IWADs directory
831 std::string iwad_par;
832 std::string iwad_file;
833 std::string iwad_dir;
834
835 const char *s = M_GetParm("-iwad");
836
837 iwad_par = std::string(s ? s : "");
838
839 if (! iwad_par.empty())
840 {
841 if (epi::FS_IsDir(iwad_par.c_str()))
842 {
843 iwad_dir = iwad_par;
844 iwad_par.clear(); // Discard
845 }
846 }
847
848 // If we haven't yet set the IWAD directory, then we check
849 // the DOOMWADDIR environment variable
850 if (iwad_dir.empty())
851 {
852 s = getenv("DOOMWADDIR");
853
854 if (s && epi::FS_IsDir(s))
855 iwad_dir = std::string(s);
856 else
857 iwad_dir = std::string("/usr/local/share/doom");
858 }
859
860 // Should the IWAD directory not be set by now, then we
861 // use our standby option of the current directory.
862 if (iwad_dir.empty())
863 iwad_dir = ".";
864
865 // Should the IWAD Parameter not be empty then it means
866 // that one was given which is not a directory. Therefore
867 // we assume it to be a name
868 if (!iwad_par.empty())
869 {
870 std::string fn = iwad_par;
871
872 // Is it missing the extension?
873 std::string ext = epi::PATH_GetExtension(iwad_par.c_str());
874 if (ext.empty())
875 {
876 fn += ("." EDGEWADEXT);
877 }
878
879 // If no directory given use the IWAD directory
880 std::string dir = epi::PATH_GetDir(fn.c_str());
881 if (dir.empty())
882 iwad_file = epi::PATH_Join(iwad_dir.c_str(), fn.c_str());
883 else
884 iwad_file = fn;
885
886 if (!epi::FS_Access(iwad_file.c_str(), epi::file_c::ACCESS_READ))
887 {
888 I_Error("IdentifyVersion: Unable to add specified '%s'", fn.c_str());
889 }
890 }
891 else
892 {
893 const char *location;
894
895 int max = 1;
896
897 if (stricmp(iwad_dir.c_str(), game_dir.c_str()) != 0)
898 {
899 // IWAD directory & game directory differ
900 // therefore do a second loop which will
901 // mean we check both.
902 max++;
903 }
904
905 bool done = false;
906 for (int i = 0; i < max && !done; i++)
907 {
908 location = (i == 0 ? iwad_dir.c_str() : game_dir.c_str());
909
910 //
911 // go through the available wad names constructing an access
912 // name for each, adding the file if they exist.
913 //
914 // -ACB- 2000/06/08 Quit after we found a file - don't load
915 // more than one IWAD
916 //
917 for (int w_idx=0; wadname[w_idx]; w_idx++)
918 {
919 std::string fn(epi::PATH_Join(location, wadname[w_idx]));
920
921 fn += ("." EDGEWADEXT);
922
923 if (epi::FS_Access(fn.c_str(), epi::file_c::ACCESS_READ))
924 {
925 iwad_file = fn;
926 done = true;
927 break;
928 }
929 }
930 }
931 }
932
933 if (iwad_file.empty())
934 I_Error("IdentifyVersion: No IWADS found!\n");
935
936 W_AddRawFilename(iwad_file.c_str(), FLKIND_IWad);
937
938 iwad_base = epi::PATH_GetBasename(iwad_file.c_str());
939
940 I_Debugf("IWAD BASE = [%s]\n", iwad_base.c_str());
941
942 // Emulate this behaviour?
943
944 // Look for the required wad in the IWADs dir and then the gamedir
945 std::string reqwad(epi::PATH_Join(iwad_dir.c_str(), REQUIREDWAD "." EDGEWADEXT));
946
947 if (! epi::FS_Access(reqwad.c_str(), epi::file_c::ACCESS_READ))
948 {
949 reqwad = epi::PATH_Join(game_dir.c_str(), REQUIREDWAD "." EDGEWADEXT);
950
951 if (! epi::FS_Access(reqwad.c_str(), epi::file_c::ACCESS_READ))
952 {
953 I_Error("IdentifyVersion: Could not find required %s.%s!\n",
954 REQUIREDWAD, EDGEWADEXT);
955 }
956 }
957
958 W_AddRawFilename(reqwad.c_str(), FLKIND_EWad);
959 }
960
CheckTurbo(void)961 static void CheckTurbo(void)
962 {
963 int turbo_scale = 100;
964
965 int p = M_CheckParm("-turbo");
966
967 if (p)
968 {
969 if (p + 1 < M_GetArgCount())
970 turbo_scale = atoi(M_GetArgument(p + 1));
971 else
972 turbo_scale = 200;
973
974 if (turbo_scale < 10) turbo_scale = 10;
975 if (turbo_scale > 400) turbo_scale = 400;
976
977 CON_MessageLDF("TurboScale", turbo_scale);
978 }
979
980 E_SetTurboScale(turbo_scale);
981 }
982
983
ShowDateAndVersion(void)984 static void ShowDateAndVersion(void)
985 {
986 time_t cur_time;
987 char timebuf[100];
988
989 time(&cur_time);
990 strftime(timebuf, 99, "%I:%M %p on %d/%b/%Y", localtime(&cur_time));
991
992 I_Debugf("[Log file created at %s]\n\n", timebuf);
993 I_Debugf("[Debug file created at %s]\n\n", timebuf);
994
995 // 23-6-98 KM Changed to hex to allow versions such as 0.65a etc
996 I_Printf("EDGE v" EDGEVERSTR " compiled on " __DATE__ " at " __TIME__ "\n");
997 I_Printf("EDGE homepage is at http://edge.sourceforge.net/\n");
998 I_Printf("EDGE is based on DOOM by id Software http://www.idsoftware.com/\n");
999
1000 #ifdef WIN32
1001 I_Printf("Executable path: '%s'\n", win32_exe_path);
1002 #endif
1003
1004 M_DebugDumpArgs();
1005 }
1006
SetupLogAndDebugFiles(void)1007 static void SetupLogAndDebugFiles(void)
1008 {
1009 // -AJA- 2003/11/08 The log file gets all CON_Printfs, I_Printfs,
1010 // I_Warnings and I_Errors.
1011
1012 std::string log_fn (epi::PATH_Join(home_dir.c_str(), EDGELOGFILE));
1013 std::string debug_fn(epi::PATH_Join(home_dir.c_str(), "debug.txt"));
1014
1015 logfile = NULL;
1016 debugfile = NULL;
1017
1018 if (! M_CheckParm("-nolog"))
1019 {
1020
1021 logfile = fopen(log_fn.c_str(), "w");
1022
1023 if (!logfile)
1024 I_Error("[E_Startup] Unable to create log file\n");
1025 }
1026
1027 //
1028 // -ACB- 1998/09/06 Only used for debugging.
1029 // Moved here to setup debug file for DDF Parsing...
1030 //
1031 // -ES- 1999/08/01 Debugfiles can now be used without -DDEVELOPERS, and
1032 // then logs all the CON_Printfs, I_Printfs and I_Errors.
1033 //
1034 // -ACB- 1999/10/02 Don't print to console, since we don't have a console yet.
1035
1036 /// int p = M_CheckParm("-debug");
1037 if (true)
1038 {
1039 debugfile = fopen(debug_fn.c_str(), "w");
1040
1041 if (!debugfile)
1042 I_Error("[E_Startup] Unable to create debugfile");
1043 }
1044 }
1045
AddSingleCmdLineFile(const char * name)1046 static void AddSingleCmdLineFile(const char *name)
1047 {
1048 std::string ext = epi::PATH_GetExtension(name);
1049 int kind = FLKIND_Lump;
1050
1051 if (stricmp(ext.c_str(), "edm") == 0)
1052 I_Error("Demos are no longer supported\n");
1053
1054 // no need to check for GWA (shouldn't be added manually)
1055
1056 if (stricmp(ext.c_str(), "wad") == 0)
1057 kind = FLKIND_PWad;
1058 else if (stricmp(ext.c_str(), "hwa") == 0)
1059 kind = FLKIND_HWad;
1060 else if (stricmp(ext.c_str(), "rts") == 0)
1061 kind = FLKIND_RTS;
1062 else if (stricmp(ext.c_str(), "ddf") == 0 ||
1063 stricmp(ext.c_str(), "ldf") == 0)
1064 kind = FLKIND_DDF;
1065 else if (stricmp(ext.c_str(), "deh") == 0 ||
1066 stricmp(ext.c_str(), "bex") == 0)
1067 kind = FLKIND_Deh;
1068
1069 std::string fn = M_ComposeFileName(game_dir.c_str(), name);
1070
1071 W_AddRawFilename(fn.c_str(), kind);
1072 }
1073
AddCommandLineFiles(void)1074 static void AddCommandLineFiles(void)
1075 {
1076 // first handle "loose" files (arguments before the first option)
1077
1078 int p;
1079 const char *ps;
1080
1081 for (p = 1; p < M_GetArgCount() && '-' != (ps = M_GetArgument(p))[0]; p++)
1082 {
1083 AddSingleCmdLineFile(ps);
1084 }
1085
1086 // next handle the -file option (we allow multiple uses)
1087
1088 p = M_CheckNextParm("-file", 0);
1089
1090 while (p)
1091 {
1092 // the parms after p are wadfile/lump names,
1093 // go until end of parms or another '-' preceded parm
1094
1095 for (p++; p < M_GetArgCount() && '-' != (ps = M_GetArgument(p))[0]; p++)
1096 {
1097 AddSingleCmdLineFile(ps);
1098 }
1099
1100 p = M_CheckNextParm("-file", p-1);
1101 }
1102
1103 // scripts....
1104
1105 p = M_CheckNextParm("-script", 0);
1106
1107 while (p)
1108 {
1109 // the parms after p are script filenames,
1110 // go until end of parms or another '-' preceded parm
1111
1112 for (p++; p < M_GetArgCount() && '-' != (ps = M_GetArgument(p))[0]; p++)
1113 {
1114 std::string ext = epi::PATH_GetExtension(ps);
1115
1116 // sanity check...
1117 if (stricmp(ext.c_str(), "wad") == 0 ||
1118 stricmp(ext.c_str(), "gwa") == 0 ||
1119 stricmp(ext.c_str(), "hwa") == 0 ||
1120 stricmp(ext.c_str(), "ddf") == 0 ||
1121 stricmp(ext.c_str(), "deh") == 0 ||
1122 stricmp(ext.c_str(), "bex") == 0)
1123 {
1124 I_Error("Illegal filename for -script: %s\n", ps);
1125 }
1126
1127 std::string fn = M_ComposeFileName(game_dir.c_str(), ps);
1128
1129 W_AddRawFilename(fn.c_str(), FLKIND_RTS);
1130 }
1131
1132 p = M_CheckNextParm("-script", p-1);
1133 }
1134
1135
1136 // finally handle the -deh option(s)
1137
1138 p = M_CheckNextParm("-deh", 0);
1139
1140 while (p)
1141 {
1142 // the parms after p are Dehacked/BEX filenames,
1143 // go until end of parms or another '-' preceded parm
1144
1145 for (p++; p < M_GetArgCount() && '-' != (ps = M_GetArgument(p))[0]; p++)
1146 {
1147 std::string ext(epi::PATH_GetExtension(ps));
1148
1149 // sanity check...
1150 if (stricmp(ext.c_str(), "wad") == 0 ||
1151 stricmp(ext.c_str(), "gwa") == 0 ||
1152 stricmp(ext.c_str(), "hwa") == 0 ||
1153 stricmp(ext.c_str(), "ddf") == 0 ||
1154 stricmp(ext.c_str(), "rts") == 0)
1155 {
1156 I_Error("Illegal filename for -deh: %s\n", ps);
1157 }
1158
1159 std::string fn = M_ComposeFileName(game_dir.c_str(), ps);
1160
1161 W_AddRawFilename(fn.c_str(), FLKIND_Deh);
1162 }
1163
1164 p = M_CheckNextParm("-deh", p-1);
1165 }
1166 }
1167
InitDDF(void)1168 static void InitDDF(void)
1169 {
1170 I_Debugf("- Initialising DDF\n");
1171
1172 DDF_Init(EDGEVER);
1173 }
1174
1175
E_EngineShutdown(void)1176 void E_EngineShutdown(void)
1177 {
1178 N_QuitNetGame();
1179
1180 S_StopMusic();
1181
1182 // Pause to allow sounds to finish
1183 for (int loop=0; loop < 30; loop++)
1184 {
1185 S_SoundTicker();
1186 I_Sleep(50);
1187 }
1188
1189 S_Shutdown();
1190 }
1191
1192 typedef struct
1193 {
1194 int prog_time; // rough indication of progress time
1195 void (*function)(void);
1196 }
1197 startuporder_t;
1198
1199 startuporder_t startcode[] =
1200 {
1201 { 1, InitDDF },
1202 { 1, IdentifyVersion },
1203 { 1, AddCommandLineFiles },
1204 { 1, CheckTurbo },
1205 { 1, RAD_Init },
1206 { 4, W_InitMultipleFiles },
1207 { 1, V_InitPalette },
1208 { 2, HU_Init },
1209 { 3, W_InitFlats },
1210 { 10, W_InitTextures },
1211 { 1, CON_Start },
1212 { 1, SpecialWadVerify },
1213 { 1, M_InitMiscConVars },
1214 { 20, W_ReadDDF },
1215 { 1, DDF_CleanUp },
1216 { 1, SetLanguage },
1217 { 1, ShowNotice },
1218 { 1, SV_MainInit },
1219 { 10, W_ImageCreateUser },
1220 { 20, W_InitSprites },
1221 { 3, W_ProcessTX_HI },
1222 { 1, W_InitModels },
1223 { 1, M_Init },
1224 { 3, R_Init },
1225 { 1, P_Init },
1226 { 1, P_MapInit },
1227 { 1, P_InitSwitchList },
1228 { 1, W_InitPicAnims },
1229 { 1, S_Init },
1230 { 1, N_InitNetwork },
1231 { 1, M_CheatInit },
1232 { 1, VM_InitCoal },
1233 { 8, VM_LoadScripts },
1234 { 0, NULL }
1235 };
1236
1237 extern void WLF_InitMaps(void); //!!!
1238
1239 // Local Prototypes
1240 static void E_Startup();
1241 static void E_Shutdown(void);
1242
1243
E_Startup(void)1244 static void E_Startup(void)
1245 {
1246 int p;
1247
1248 // Version check ?
1249 if (M_CheckParm("-version"))
1250 {
1251 // -AJA- using I_Error here, since I_Printf crashes this early on
1252 I_Error("\nEDGE version is " EDGEVERSTR "\n");
1253 }
1254
1255 // -AJA- 2000/02/02: initialise global gameflags to defaults
1256 global_flags = default_gameflags;
1257
1258
1259 InitDirectories();
1260
1261 SetupLogAndDebugFiles();
1262
1263 CON_InitConsole();
1264 CON_ResetAllVars();
1265
1266 ShowDateAndVersion();
1267
1268 M_LoadDefaults();
1269
1270 CON_HandleProgramArgs();
1271 SetGlobalVars();
1272
1273 DoSystemStartup();
1274
1275 I_PutTitle(E_TITLE); // Needs to be done once the system is up and running
1276
1277 // RGL_FontStartup();
1278
1279 E_GlobalProgress(0, 0, 1);
1280
1281 int total=0;
1282 int cur=0;
1283
1284 for (p=0; startcode[p].function != NULL; p++)
1285 total += startcode[p].prog_time;
1286
1287 // Cycle through all the startup functions
1288 for (p=0; startcode[p].function != NULL; p++)
1289 {
1290 E_GlobalProgress(cur, startcode[p].prog_time, total);
1291
1292 startcode[p].function();
1293
1294 cur += startcode[p].prog_time;
1295 }
1296
1297 E_GlobalProgress(100, 0, 100);
1298 }
1299
1300
E_Shutdown(void)1301 static void E_Shutdown(void)
1302 {
1303 /* TODO: E_Shutdown */
1304 }
1305
1306
E_InitialState(void)1307 static void E_InitialState(void)
1308 {
1309 I_Debugf("- Setting up Initial State...\n");
1310
1311 const char *ps;
1312
1313 // do demos and loadgames first, as they contain all of the
1314 // necessary state already (in the demo file / savegame).
1315
1316 if (M_CheckParm("-playdemo") || M_CheckParm("-timedemo") ||
1317 M_CheckParm("-record"))
1318 {
1319 I_Error("Demos are no longer supported\n");
1320 }
1321
1322 ps = M_GetParm("-loadgame");
1323 if (ps)
1324 {
1325 G_DeferredLoadGame(atoi(ps));
1326 return;
1327 }
1328
1329 bool warp = false;
1330
1331 // get skill / episode / map from parms
1332 std::string warp_map;
1333 skill_t warp_skill = sk_medium;
1334 int warp_deathmatch = 0;
1335
1336 int bots = 0;
1337
1338 ps = M_GetParm("-bots");
1339 if (ps)
1340 bots = atoi(ps);
1341
1342 ps = M_GetParm("-warp");
1343 if (ps)
1344 {
1345 warp = true;
1346 warp_map = std::string(ps);
1347 }
1348
1349 // -KM- 1999/01/29 Use correct skill: 1 is easiest, not 0
1350 ps = M_GetParm("-skill");
1351 if (ps)
1352 {
1353 warp = true;
1354 warp_skill = (skill_t)(atoi(ps) - 1);
1355 }
1356
1357 // deathmatch check...
1358 int pp = M_CheckParm("-deathmatch");
1359 if (pp)
1360 {
1361 warp_deathmatch = 1;
1362
1363 if (pp + 1 < M_GetArgCount())
1364 warp_deathmatch = MAX(1, atoi(M_GetArgument(pp + 1)));
1365 }
1366 else if (M_CheckParm("-altdeath") > 0)
1367 {
1368 warp_deathmatch = 2;
1369 }
1370
1371
1372 if (M_GetParm("-record"))
1373 warp = true;
1374
1375 // start the appropriate game based on parms
1376 if (! warp)
1377 {
1378 I_Debugf("- Startup: showing title screen.\n");
1379 E_StartTitle();
1380 return;
1381 }
1382
1383 newgame_params_c params;
1384
1385 params.skill = warp_skill;
1386 params.deathmatch = warp_deathmatch;
1387
1388 if (warp_map.length() > 0)
1389 params.map = G_LookupMap(warp_map.c_str());
1390 else
1391 params.map = G_LookupMap("1");
1392
1393 if (! params.map)
1394 I_Error("-warp: no such level '%s'\n", warp_map.c_str());
1395
1396 SYS_ASSERT(G_MapExists(params.map));
1397 SYS_ASSERT(params.map->episode);
1398
1399 params.random_seed = I_PureRandom();
1400
1401 params.SinglePlayer(bots);
1402
1403 G_DeferredNewGame(params);
1404 }
1405
1406
1407 //
1408 // ---- MAIN ----
1409 //
1410 // -ACB- 1998/08/10 Removed all reference to a gamemap, episode and mission
1411 // Used LanguageLookup() for lang specifics.
1412 //
1413 // -ACB- 1998/09/06 Removed all the unused code that no longer has
1414 // relevance.
1415 //
1416 // -ACB- 1999/09/04 Removed statcopy parm check - UNUSED
1417 //
1418 // -ACB- 2004/05/31 Moved into a namespace, the c++ revolution begins....
1419 //
E_Main(int argc,const char ** argv)1420 void E_Main(int argc, const char **argv)
1421 {
1422 // Start the EPI Interface
1423 epi::Init();
1424
1425 // Start memory allocation system at the very start (SCHEDULED FOR REMOVAL)
1426 Z_Init();
1427
1428 // Implemented here - since we need to bring the memory manager up first
1429 // -ACB- 2004/05/31
1430 M_InitArguments(argc, argv);
1431
1432 try
1433 {
1434 E_Startup();
1435
1436 E_InitialState();
1437
1438 CON_MessageColor(RGB_MAKE(255,255,0));
1439 I_Printf("EDGE v" EDGEVERSTR " initialisation complete.\n");
1440
1441 I_Debugf("- Entering game loop...\n");
1442
1443 while (! (app_state & APP_STATE_PENDING_QUIT))
1444 {
1445 // We always do this once here, although the engine may
1446 // makes in own calls to keep on top of the event processing
1447 I_ControlGetEvents();
1448
1449 if (app_state & APP_STATE_ACTIVE)
1450 E_Tick();
1451 }
1452 }
1453 catch(...)
1454 {
1455 I_Error("Unexpected internal failure occurred!\n");
1456 }
1457
1458 E_Shutdown(); // Shutdown whatever at this point
1459
1460 // Kill the epi interface
1461 epi::Shutdown();
1462 }
1463
1464
1465 //
1466 // Called when this application has lost focus (i.e. an ALT+TAB event)
1467 //
E_Idle(void)1468 void E_Idle(void)
1469 {
1470 E_ReleaseAllKeys();
1471 }
1472
1473
1474 //
1475 // This Function is called for a single loop in the system.
1476 //
1477 // -ACB- 1999/09/24 Written
1478 // -ACB- 2004/05/31 Namespace'd
1479 //
E_Tick(void)1480 void E_Tick(void)
1481 {
1482 // -ES- 1998/09/11 It's a good idea to frequently check the heap
1483 #ifdef DEVELOPERS
1484 //Z_CheckHeap();
1485 #endif
1486
1487 G_BigStuff();
1488
1489 // Update display, next frame, with current state.
1490 E_Display();
1491
1492 bool fresh_game_tic;
1493
1494 // this also runs the responder chain via E_ProcessEvents
1495 int counts = N_TryRunTics(&fresh_game_tic);
1496
1497 SYS_ASSERT(counts > 0);
1498
1499 for (; counts > 0; counts--) // run the tics
1500 {
1501 CON_Ticker();
1502 M_Ticker();
1503
1504 if (fresh_game_tic)
1505 G_Ticker();
1506
1507 S_SoundTicker();
1508 S_MusicTicker(); // -ACB- 1999/11/13 Improved music update routines
1509
1510 N_NetUpdate(); // check for new console commands
1511 }
1512 }
1513
1514 //--- editor settings ---
1515 // vi:ts=4:sw=4:noexpandtab
1516