1 // Crimson Fields -- a hex-based game of tactical warfare
2 // Copyright (C) 2000-2007 Jens Granseuer
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 //
18 
19 /////////////////////////////////////////////////////////////////////
20 // main.cpp -- Crimson Fields
21 /////////////////////////////////////////////////////////////////////
22 
23 #include <stdlib.h>
24 #include <string.h>
25 #include <iostream>
26 #include <fstream>
27 
28 #ifdef _WIN32_WCE
29 # define time(n) GetTickCount()
30 #else
31 # include <time.h>
32 #endif
33 
34 #include "SDL.h"
35 
36 #include "view.h"
37 #include "game.h"
38 #include "misc.h"
39 #include "fileio.h"
40 #include "initwindow.h"
41 #include "globals.h"
42 #include "sound.h"
43 #include "options.h"
44 #include "strutil.h"
45 #include "msgs.h"
46 #include "network.h"
47 #include "platform.h"
48 
49 // global vars
50 Game *Gam;
51 Image *Images[NUM_IMAGES] = { NULL };
52 Language Lang;
53 
54 // global options, can be accessed from everywhere
55 Options CFOptions;
56 
57 // local vars
58 static View *display;
59 
60 // local function prototypes
61 static void parse_options( int argc, char **argv, GUIOptions &opts );
62 static void print_usage( char *prog );
63 static View *init( GUIOptions &opts );
64 static bool init_locale( void );
65 static void load_settings( GUIOptions &opts );
66 static void save_settings( View *display );
67 static void set_icon( const Surface &s, const Rect &icon );
68 static GUI_Status event_filter( SDL_Event &event, Window *window );
69 static void do_exit( void );
70 
71 // Start of program functions //////////////////////////////////////////
72 
73 #ifdef _MSC_VER
WinMain(HINSTANCE hInst,HINSTANCE hPrev,LPTSTR szCmdLine,int sw)74 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPTSTR szCmdLine, int sw) {
75   int argc = __argc;
76   char **argv = __argv;
77 #else
78 int main( int argc, char **argv ) {
79 #endif
80   struct GUIOptions guiopts = { DEFAULT_RESOLUTION, DISPLAY_BPP, true, true,
81                     MIX_MAX_VOLUME*3/4, MIX_MAX_VOLUME/2, SDL_HWSURFACE, NULL };
82 
83   load_settings( guiopts );
84 
85   if ( !platform_init( guiopts ) ) return 0;
86 
87   parse_options( argc, argv, guiopts );
88 
89   display = init( guiopts );
90   if ( display && platform_setup( display ) ) {
91 
92     // only open intro screen if the user didn't supply a level on the command line
93     int intro = 1;
94 
95     if ( guiopts.level ) {
96       Gam = new Game( display );
97       intro = Gam->Load( guiopts.level );
98       Mission *m = Gam->GetMission();
99       if ( !intro ) {
100         // default is to play single-player single-map
101         if ( !(m->GetFlags() & GI_SAVEFILE) )
102           m->GetPlayer(PLAYER_ONE).SetType( HUMAN );
103         Gam->InitWindows();
104         Gam->StartTurn();
105       } else {
106         delete Gam;
107         Gam = NULL;
108         intro = 1;
109       }
110     }
111 
112     GUI_Status status;
113     do {
114       if ( intro ) {
115         display->Refresh();
116         TitleWindow *twin = new TitleWindow( display );
117 
118         // preload main window, levels, etc.
119         InitWindow *iwin = new InitWindow( display, twin );
120 
121         // if title image was successfully loaded, wait for click
122         if ( twin->Width() > 0 ) {
123           display->SelectWindow( twin );
124           twin->EventLoop();
125           display->SelectWindow( iwin );
126         }
127 
128         iwin->Show();
129       } else intro = 1;
130 
131       do {
132         status = display->HandleEvents();
133       } while ( (status != GUI_QUIT) && (status != GUI_RESTART) );
134 
135       delete Gam;
136       Gam = NULL;
137       display->CloseAllWindows();
138     } while ( status != GUI_QUIT );
139   }
140 
141   do_exit();
142   return 0;
143 }
144 
145 ////////////////////////////////////////////////////////////////////////
146 // NAME       : parse_options
147 // DESCRIPTION: Process any options given to the program on the command
148 //              line.
149 // PARAMETERS : argc - argument count
150 //              argv - pointer to array of arguments
151 //              opts - buffer to store GUI and audio options; should be
152 //                     initialized with defaults before calling this
153 //                     method
154 // RETURNS    : -
155 ////////////////////////////////////////////////////////////////////////
156 
157 static void parse_options( int argc, char **argv, GUIOptions &opts ) {
158 
159   while ( argc > 1 ) {
160     --argc;
161 
162     if (strcmp(argv[argc-1], "--width") == 0) {
163       opts.px_width = atoi(argv[argc]);
164     } else if (strcmp(argv[argc-1], "--height") == 0) {
165       opts.px_height = atoi(argv[argc]);
166     } else if (strcmp(argv[argc-1], "--level") == 0) {
167       opts.level = argv[argc];
168     } else if (strcmp(argv[argc-1], "--fullscreen") == 0) {
169       if ( atoi( argv[argc] ) ) opts.sdl_flags |= SDL_FULLSCREEN;
170       else opts.sdl_flags &= ~SDL_FULLSCREEN;
171     } else if (strcmp(argv[argc-1], "--sound") == 0) {
172       if ( atoi( argv[argc] ) ) opts.sfx = opts.music = true;
173       else opts.sfx = opts.music = false;
174     } else {
175       if (strcmp(argv[argc], "--version") == 0)
176         cout << PROGRAMNAME " " VERSION << endl;
177       else print_usage( argv[0] );
178       exit ( 0 );
179     }
180     --argc;
181   }
182   if ( opts.px_width < MIN_XRES ) opts.px_width = MIN_XRES;
183   if ( opts.px_height < MIN_YRES ) opts.px_height = MIN_YRES;
184 }
185 
186 ////////////////////////////////////////////////////////////////////////
187 // NAME       : print_usage
188 // DESCRIPTION: Print a usage message to stdout.
189 // PARAMETERS : prog - program name as given on the command line
190 // RETURNS    : -
191 ////////////////////////////////////////////////////////////////////////
192 
193 static void print_usage( char *prog ) {
194   cout << "Usage: " << prog << " [options]" << endl << endl
195             << "Available options:" << endl
196             << "  --level <level>      load level or save file" << endl
197             << "  --width <width>      set screen width" << endl
198             << "  --height <height>    set screen height" << endl
199             << "  --fullscreen <1|0>   enable/disable fullscreen mode" << endl
200 #ifndef DISABLE_SOUND
201             << "  --sound <1|0>        enable/disable sound" << endl
202 #endif
203             << "  --help               display this help and exit" << endl
204             << "  --version            output version information and exit" << endl;
205 }
206 
207 ////////////////////////////////////////////////////////////////////////
208 // NAME       : init
209 // DESCRIPTION: Initialize the display, the system icons surface and the
210 //              game fonts.
211 // PARAMETERS : opts - contains the user settings for the sound and
212 //                     graphics subsystems
213 // RETURNS    : a pointer to the display surface on success, NULL
214 //              otherwise
215 ////////////////////////////////////////////////////////////////////////
216 
217 static View *init( GUIOptions &opts ) {
218   View *view = NULL;
219   Font *f1 = NULL, *f2 = NULL;
220   Surface *icons = NULL;
221   bool ok = false;
222 
223   if ( SDL_Init( SDL_INIT_VIDEO ) >= 0 ) {
224 
225     // load locale
226     if ( !init_locale() ) return NULL;
227 
228     string datpath( get_data_dir() );
229     datpath.append( CF_DATFILE );
230     File datfile( datpath );
231     if ( datfile.Open( "rb" ) ) {
232 
233       icons = new Surface;    // load icons surface
234       if ( !icons->LoadImageData( datfile ) ) {
235 
236         set_icon( *icons, Rect(64, 0, 32, 32) );
237 
238         view = new View( opts.px_width, opts.px_height, opts.bpp,
239                          opts.sdl_flags );
240         if ( view->s_surface && !TTF_Init()) {  // load fonts
241           int smallsize, largesize;
242 
243           // use smaller fonts on small displays to prevent text clipping
244           if ((opts.px_width <= 240) || (opts.px_height <= 320)) {
245             smallsize = CF_FONT_LOWRES_SMALL;
246             largesize = CF_FONT_LOWRES_LARGE;
247           } else {
248             smallsize = CF_FONT_SMALL;
249             largesize = CF_FONT_LARGE;
250           }
251 
252           datpath = get_data_dir() + CF_FONT;
253           f1 = new Font();
254           f2 = new Font();
255 
256           if ( !f1->Load( datpath.c_str(), smallsize ) &&
257                !f2->Load( datpath.c_str(), largesize ) )
258             ok = true;
259           else cerr << "Error: Couldn't load font " << datpath << endl;
260         } else cerr << "Error: Couldn't set video mode (" << SDL_GetError() << ')' << endl;
261 
262       } else cerr << "Error: Couldn't read data file" << endl;
263     } else cerr << "Error: Couldn't open " << datpath << endl;
264   } else cerr << "Error: Couldn't initialize ( " << SDL_GetError() << ')' << endl;
265 
266   if ( ok ) {
267     // set main window title
268     SDL_WM_SetCaption( PROGRAMNAME, PROGRAMNAME );
269     SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL );
270 
271     srand( time(0) );        // initialize random number generator
272 
273     Audio::InitSfx( opts.sfx, opts.sfx_vol );  // load sound effects
274     Audio::InitMusic( opts.music, opts.music_vol );
275 
276     Network::Init();        // initialize networking subsystem
277 
278     for ( int i = 0; i <= XP_MAX_LEVEL; ++i )
279       Images[ICON_XP_BASE+i] = new Image( icons, 64 + i * XP_ICON_WIDTH, 32, XP_ICON_WIDTH, XP_ICON_HEIGHT );
280 
281     icons->DisplayFormat();
282 
283     view->SetSmallFont( f1 );
284     view->SetLargeFont( f2 );
285     view->SetFGPen( Color(CF_COLOR_HIGHLIGHT) );
286     view->SetBGPen( Color(CF_COLOR_SHADOW) );
287     view->SetEventFilter( event_filter );
288     view->SetSystemIcons( icons );
289 
290     // create options and save games directories if possible
291     create_config_dir();
292 
293     return view;
294   } else {
295     delete icons;
296     delete view;
297     delete f1;
298     delete f2;
299     return NULL;
300   }
301 }
302 
303 ////////////////////////////////////////////////////////////////////////
304 // NAME       : init_locale
305 // DESCRIPTION: Load the strings from a file.
306 // PARAMETERS : -
307 // RETURNS    : TRUE on success, FALSE otherwise
308 ////////////////////////////////////////////////////////////////////////
309 
310 static bool init_locale( void ) {
311   bool rc = false, retry = false;
312 
313   const string ldir( get_locale_dir() );
314   string lid( CFOptions.GetLanguage() );
315 
316   // try the user locale first, and use the default if that fails
317   do {
318     string lfile( ldir + lid + ".dat" );
319     short num = Lang.ReadCatalog( lfile.c_str() );
320     if ( num == -1 ) {
321       cerr << "Error: Couldn't load language resources for language '"
322                 << lid << "'" << endl;
323     } else if ( num != CF_MSGS ) {
324       cerr << "Error: Language catalog for '" << lid << "' contains "
325                 << num << " strings, expected " << CF_MSGS << endl;
326     } else rc = true;
327 
328     // only retry once
329     if ( (rc == false) && (retry == false) &&
330          (lid != CF_LANG_DEFAULT) ) {
331       lid.assign( CF_LANG_DEFAULT );
332       cerr << "Using default locale (" << lid << ") instead" << endl;
333       retry = true;
334     } else {
335       retry = false;
336 
337       if ( rc == true ) {
338         // change the language setting if necessary
339         if ( lid != CFOptions.GetLanguage() )
340           CFOptions.SetLanguage( lid.c_str() );
341       }
342     }
343   } while ( retry );
344 
345   return rc;
346 }
347 
348 ////////////////////////////////////////////////////////////////////////
349 // NAME       : load_settings
350 // DESCRIPTION: Read default display settings from the crimsonrc file.
351 // PARAMETERS : opts - buffer to store the settings. These should
352 //                     already be initialized with some defaults in
353 //                     case the rc file doesn't exist or this function
354 //                     fails.
355 // RETURNS    : -
356 ////////////////////////////////////////////////////////////////////////
357 
358 static void load_settings( GUIOptions &opts ) {
359   string crimsonrc( get_config_dir() );
360   crimsonrc.append( CRIMSONRC );
361 
362   ifstream file( crimsonrc.c_str() );
363   if ( file.is_open() ) {
364     string buf;
365     const char *val, *linebuf;
366     unsigned int line = 0;
367     size_t pos;
368 
369     while (!file.eof()) {
370       getline(file, buf);
371       ++line;
372 
373       if ( buf.size() > 0 ) { // ignore empty lines
374         pos = buf.find( ' ' );
375         if ( pos != string::npos ) {
376           linebuf = buf.c_str();
377           val = &linebuf[pos+1];
378           while ( *val == ' ' ) ++val;
379 
380           if ( !strncmp( linebuf, "width", 5 ) ) opts.px_width = atoi(val);
381           else if ( !strncmp( linebuf, "height", 6 ) ) opts.px_height = atoi(val);
382           else if ( !strncmp( linebuf, "fullscreen", 10 ) ) {
383             if ( atoi(val) != 0 ) opts.sdl_flags |= SDL_FULLSCREEN;
384           } else if ( !strncmp( linebuf, "sfxvol", 6 ) ) opts.sfx_vol = atoi(val);
385           else if ( !strncmp( linebuf, "musicvol", 8 ) ) opts.music_vol = atoi(val);
386           else if ( !strncmp( linebuf, "sfx", 3 ) ) opts.sfx = (atoi(val) != 0);
387           else if ( !strncmp( linebuf, "music", 5 ) ) opts.music = (atoi(val) != 0);
388           else if ( !strncmp( linebuf, "locale", 6 ) ) CFOptions.SetLanguage(val);
389           else if ( !strncmp( linebuf, "showdamage", 10 ) ) CFOptions.SetDamageIndicator( atoi(val) != 0 );
390           else if ( !strncmp( linebuf, "unlock", 6 ) ) CFOptions.Unlock( val );
391           else if ( !strncmp( linebuf, "listen", 6 ) ) CFOptions.SetLocalPort( atoi(val) );
392           else if ( !strncmp( linebuf, "showreplay", 10 ) ) {
393             int rep = atoi(val);
394             CFOptions.SetTurnReplay( rep != 0 );
395             CFOptions.SetQuickReplay( rep == 1 );
396           } else if ( !strncmp( linebuf, "server", 6 ) ) {
397             pos = buf.find( ':', val - linebuf );
398             if ( pos != string::npos )  {
399               buf[pos] = '\0';
400               CFOptions.SetRemotePort( atoi(&linebuf[pos+1]) );
401             }
402             CFOptions.SetRemoteName( val );
403           } else if ( !strncmp( linebuf, "keymap", 6 ) ) {
404             SDLKey key;
405             char *endptr;
406 
407             for (int i = 0; i < KEYBIND_COUNT && *val; ++i) {
408               key = (SDLKey)strtoul( val, &endptr, 10 );
409               if ((endptr == NULL) || (*endptr != ';'))
410                 break;
411               CFOptions.SetKeyBinding( (KeyBinding)i, key );
412               val = endptr + 1;
413             }
414           } else cerr << "Warning: unrecognized config option in line " << line << endl;
415         }
416       }
417     }
418     file.close();
419   }
420 }
421 
422 ////////////////////////////////////////////////////////////////////////
423 // NAME       : save_settings
424 // DESCRIPTION: Save current display settings to the crimsonrc file.
425 // PARAMETERS : display - pointer to display
426 // RETURNS    : -
427 ////////////////////////////////////////////////////////////////////////
428 
429 static void save_settings( View *display ) {
430   const SDLKey *keymap = CFOptions.GetKeyBindings();
431   string crimsonrc( get_config_dir() );
432   crimsonrc.append( CRIMSONRC );
433 
434   ofstream file( crimsonrc.c_str() );
435   if ( file.is_open() ) {
436     file << "width " << display->Width() << '\n';
437     file << "height " << display->Height() << '\n';
438     file << StringUtil::strprintf("fullscreen %d", display->IsFullScreen()) << '\n';
439     file << StringUtil::strprintf("sfx %d", Audio::GetSfxState()) << '\n';
440     file << StringUtil::strprintf("music %d", Audio::GetMusicState()) << '\n';
441     file << StringUtil::strprintf("sfxvol %d", Audio::GetSfxVolume()) << '\n';
442     file << StringUtil::strprintf("musicvol %d", Audio::GetMusicVolume()) << '\n';
443     file << "locale " << CFOptions.GetLanguage() << '\n';
444     file << StringUtil::strprintf("showdamage %d", CFOptions.GetDamageIndicator()) << '\n';
445     file << StringUtil::strprintf("showreplay %d",
446             CFOptions.GetTurnReplay() ? (CFOptions.GetQuickReplay() ? 1 : 2) : 0 ) << '\n';
447     if ( CFOptions.GetRemoteName() ) {
448       file << StringUtil::strprintf(
449               StringUtil::strprintf("server %s:%d", CFOptions.GetRemotePort()),
450               CFOptions.GetRemoteName()) << '\n';
451     }
452     file << StringUtil::strprintf("listen %d", CFOptions.GetLocalPort()) << '\n';
453 
454     file << "keymap ";
455     for (int i = 0; i < KEYBIND_COUNT; ++i)
456       file << keymap[i] << ';';
457     file << '\n';
458 
459     const vector<string> &maps = CFOptions.GetUnlockedMaps();
460     for ( vector<string>::const_iterator i = maps.begin(); i != maps.end(); ++i )
461       file << "unlock " << *i << '\n';
462 
463     file.close();
464   }
465 }
466 
467 ////////////////////////////////////////////////////////////////////////
468 // NAME       : set_icon
469 // DESCRIPTION: Set the application icon.
470 // PARAMETERS : s    - icon surface
471 //              icon - icon position and size on the surface
472 // RETURNS    : -
473 ////////////////////////////////////////////////////////////////////////
474 
475 static void set_icon( const Surface &s, const Rect &icon ) {
476   Surface is;
477   is.Create( icon.Width(), icon.Height(), DISPLAY_BPP, 0 );
478   is.SetColorKey( s.GetColorKey() );
479   is.Flood( is.GetColorKey() );
480   s.Blit( &is, icon, 0, 0 );
481 
482   SDL_WM_SetIcon( is.s_surface, NULL );
483 }
484 
485 ////////////////////////////////////////////////////////////////////////
486 // NAME       : event_filter
487 // DESCRIPTION: This is the global event filter function. It is hooked
488 //              to the display and called everytime the event handler
489 //              receives an event.
490 // PARAMETERS : event  - event received by the event handler
491 //              window - pointer to the currently active window
492 // RETURNS    : GUI_Status; if the filter returns GUI_NONE the event
493 //              handler will not pass the event to its windows, but
494 //              silently drop it.
495 ////////////////////////////////////////////////////////////////////////
496 
497 static GUI_Status event_filter( SDL_Event &event, Window *window ) {
498   GUI_Status rc = GUI_OK;
499 
500   if ( event.type == SDL_KEYDOWN ) {
501     rc = GUI_NONE;
502 
503     switch ( event.key.keysym.sym ) {
504     case SDLK_F11:            // toggle sound
505       Audio::ToggleSfxState();
506       break;
507     default:
508       if ( event.key.keysym.sym == CFOptions.GetKeyBindings()[KEYBIND_MINIMIZE] )
509         SDL_WM_IconifyWindow();
510       else rc = GUI_OK;            // send to windows
511     }
512   } else if ( event.type == SDL_QUIT ) do_exit();
513 
514   return rc;
515 }
516 
517 ////////////////////////////////////////////////////////////////////////
518 // NAME       : do_exit
519 // DESCRIPTION: Free all resources and exit the program.
520 // PARAMETERS : -
521 // RETURNS    : -
522 ////////////////////////////////////////////////////////////////////////
523 
524 static void do_exit( void ) {
525   delete Gam;
526 
527   platform_dispose();
528 
529   if ( display ) {
530     save_settings( display );
531     delete display;
532   }
533 
534   Network::Shutdown();
535 
536   Audio::ShutdownSfx();
537   Audio::ShutdownMusic();
538 
539   for ( int i = 0; i < NUM_IMAGES; ++i ) delete Images[i];
540 
541   TTF_Quit();
542   SDL_Quit();
543 
544   platform_shutdown();
545 
546   exit( 0 );
547 }
548 
549