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