1 // Crimson Fields -- a 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 // game.cpp
21 ////////////////////////////////////////////////////////////////////////
22 
23 #include <iostream>
24 #include <string.h>
25 
26 #include "game.h"
27 #include "path.h"
28 #include "unitwindow.h"
29 #include "gamewindow.h"
30 #include "filewindow.h"
31 #include "initwindow.h"
32 #include "fileio.h"
33 #include "sound.h"
34 #include "ai.h"
35 #include "options.h"
36 #include "strutil.h"
37 #include "msgs.h"
38 
39 extern Options CFOptions;
40 
41 // button identifiers used by the game hook function dispatcher
42 enum {
43   G_BUTTON_END_TURN = 10,
44   G_BUTTON_MAP,
45   G_BUTTON_BRIEFING,
46   G_BUTTON_SAVE,
47   G_BUTTON_LEV_INFO,
48   G_BUTTON_GENERAL_OPTIONS,
49   G_BUTTON_LANGUAGE_OPTIONS,
50   G_BUTTON_VIDEO_OPTIONS,
51   G_BUTTON_SOUND_OPTIONS,
52   G_BUTTON_KEYBOARD_OPTIONS,
53   G_BUTTON_ABORT,
54   G_BUTTON_QUIT,
55 
56   G_BUTTON_SAVE_AND_SHUTDOWN,
57   G_BUTTON_SHUTDOWN,
58 
59   G_BUTTON_UNIT_INFO,
60   G_BUTTON_UNIT_CONTENT,
61   G_BUTTON_UNIT_SWEEP,
62   G_BUTTON_UNIT_UNDO,
63   G_BUTTON_MINIMIZE_WINDOW
64 };
65 
66 enum {
67   G_KEY_QUIT = 0,
68   G_KEY_INFO,
69   G_KEY_MAP,
70   G_KEY_END_TURN,
71   G_KEY_CONTENT,
72   G_KEY_SWEEP
73 };
74 
75 #ifndef DISABLE_NETWORK
76 class NetworkProgressWindow : public ProgressWindow {
77 public:
78   NetworkProgressWindow( unsigned short w, unsigned short h,
79                          const char *msg, View *view );
80 
81   bool Cancelled( void );
82 
83 private:
84   bool cancelled;
85 };
86 
NetworkProgressWindow(unsigned short w,unsigned short h,const char * msg,View * view)87 NetworkProgressWindow::NetworkProgressWindow( unsigned short w,
88                        unsigned short h, const char *msg, View *view ) :
89        ProgressWindow( 0, 0, w, h, 0, 100, msg,
90                        WIN_CENTER|WIN_PROG_ABORT, view ), cancelled(false) {}
91 
Cancelled(void)92 bool NetworkProgressWindow::Cancelled( void ) {
93   if ( !cancelled ) {
94     if ( Get() < 100 ) Advance( 1 );
95     else Set( 0 );
96 
97     bool cancel = ProgressWindow::Cancelled();
98 
99     if ( cancel ) {
100       // ask for confirmation
101       string buttons;
102       buttons.append( MSG(MSG_B_YES) );
103       buttons += '|';
104       buttons.append( MSG(MSG_B_NO) );
105       DialogWindow *req = new DialogWindow( NULL,
106           MSG(MSG_ASK_ABORT_NETWORK), buttons, 1, 0, view );
107       req->SetButtonID( 1, 0 );
108       cancelled = (req->EventLoop() == GUI_CLOSE);
109       view->CloseWindow( req );
110     }
111   }
112 
113   return cancelled;
114 }
115 #endif
116 
117 ////////////////////////////////////////////////////////////////////////
118 // NAME       : Game::Game
119 // DESCRIPTION: Create a new game.
120 // PARAMETERS : view - the display surface
121 // RETURNS    : -
122 ////////////////////////////////////////////////////////////////////////
123 
Game(View * view)124 Game::Game( View *view ) : mission(0), mwin(0), unit(0), shader(0), view(view) {
125   InitKeys();
126 
127 #ifndef DISABLE_NETWORK
128   peer = NULL;
129 #endif
130 }
131 
132 ////////////////////////////////////////////////////////////////////////
133 // NAME       : Game::~Game
134 // DESCRIPTION: Destroy the game.
135 // PARAMETERS : -
136 // RETURNS    : -
137 ////////////////////////////////////////////////////////////////////////
138 
~Game(void)139 Game::~Game( void ) {
140   if ( mwin ) view->CloseWindow( mwin );
141   delete mission;
142   delete shader;
143 
144 #ifndef DISABLE_NETWORK
145   delete peer;
146 #endif
147 }
148 
149 ////////////////////////////////////////////////////////////////////////
150 // NAME       : Game::InitKeys
151 // DESCRIPTION: Initialize the array of keyboard commands. These
152 //              shortcuts change with the locale the user selected.
153 // PARAMETERS : -
154 // RETURNS    : -
155 ////////////////////////////////////////////////////////////////////////
156 
InitKeys(void)157 void Game::InitKeys( void ) {
158   short labels[] = { MSG_B_QUIT, MSG_B_UNIT_INFO, MSG_B_MAP, MSG_B_END_TURN,
159                      MSG_B_UNIT_CONTENT, MSG_B_UNIT_SWEEP };
160   for ( int i = 0; i <= G_KEY_SWEEP; ++i ) {
161     string label( MSG(labels[i]) );
162     size_t pos = label.find( '_' );
163     if ( pos != string::npos ) keys[i] = StringUtil::utf8chartoascii(&label[pos+1]);
164     else keys[i] = 0;
165   }
166 }
167 
168 ////////////////////////////////////////////////////////////////////////
169 // NAME       : Game::InitWindows
170 // DESCRIPTION: Initialize the game windows. We need a valid mission
171 //              loaded to do that.
172 // PARAMETERS : -
173 // RETURNS    : -
174 ////////////////////////////////////////////////////////////////////////
175 
InitWindows(void)176 void Game::InitWindows( void ) {
177   if ( mission ) {
178     mwin = new MapWindow( 0, 0, view->Width(), view->Height(), 0, view );
179     if ( CFOptions.GetDamageIndicator() ) mwin->GetMapView()->EnableUnitStats();
180     mwin->GetMapView()->SetMap( &mission->GetMap() );  // attach map to map window
181     shader = new MoveShader( &mission->GetMap(), mission->GetUnits(),
182                              mwin->GetMapView()->GetFogBuffer() );
183     ExecPreStartEvents();
184   }
185 }
186 
187 ////////////////////////////////////////////////////////////////////////
188 // NAME       : Game::Quit
189 // DESCRIPTION: Ask the user for confirmation and exit.
190 // PARAMETERS : -
191 // RETURNS    : -
192 ////////////////////////////////////////////////////////////////////////
193 
Quit(void) const194 void Game::Quit( void ) const {
195   Audio::PlaySfx( Audio::SND_GUI_ASK, 0 );
196   string buttons;
197   buttons.append( MSG(MSG_B_YES) );
198   buttons += '|';
199   buttons.append( MSG(MSG_B_NO) );
200 
201   DialogWindow *req = new DialogWindow( NULL, MSG(MSG_ASK_QUIT),
202                       buttons, 1, 0, view );
203   req->SetButtonID( 0, GUI_QUIT );
204 }
205 
206 ////////////////////////////////////////////////////////////////////////
207 // NAME       : Game::CreateSaveFileName
208 // DESCRIPTION: Get the name of the file to save to. Pop up a file
209 //              requester if necessary.
210 // PARAMETERS : filename - default filename
211 // RETURNS    : file name or empty string on error
212 ////////////////////////////////////////////////////////////////////////
213 
CreateSaveFileName(const char * filename) const214 string Game::CreateSaveFileName( const char *filename ) const {
215   string file;
216   string yesno;
217   yesno.append( MSG(MSG_B_YES) );
218   yesno += '|';
219   yesno.append( MSG(MSG_B_NO) );
220 
221   if ( filename ) file.append( filename );
222 
223   bool filesel;
224   do {
225     GUI_Status rc;
226     DialogWindow *dw;
227     filesel = false;
228 
229     if ( file.length() == 0 ) filesel = true;
230     else if ( File::Exists( file ) ) {
231       // if file exists let user confirm the write
232       string conmsg = StringUtil::strprintf( MSG(MSG_ASK_OVERWRITE), file );
233 
234       dw = new DialogWindow( NULL, conmsg, yesno, 1, 0, view );
235       dw->SetButtonID( 0, 1 );
236       dw->SetButtonID( 1, 0 );
237       rc = dw->EventLoop();
238       if ( rc == 0 ) filesel = true;
239       view->CloseWindow( dw );
240     }
241 
242     if ( filesel ) {
243       bool done = false;
244       FileWindow *fw = new FileWindow( get_save_dir().c_str(), last_file_name.c_str(),
245                                        ".sav", WIN_FILE_SAVE, view );
246       fw->ok->SetID( 1 );
247       fw->cancel->SetID( 0 );
248 
249       do {
250         rc = fw->EventLoop();
251 
252         if ( rc == 1 ) {
253           file = fw->GetFile();
254           if ( file.length() != 0 ) {
255             view->CloseWindow( fw );
256             done = true;
257           }
258         } else if ( rc == 0 ) {
259           if ( mission->GetFlags() & GI_PBEM ) {
260             // if saving a PBeM game is aborted the game data is lost...
261             dw = new DialogWindow( MSG(MSG_WARNING), MSG(MSG_ASK_ABORT_PBEM),
262                                    yesno, 1, WIN_FONT_BIG, view );
263             Audio::PlaySfx( Audio::SND_GUI_ASK, 0 );
264             dw->SetButtonID( 0, 1 );
265             dw->SetButtonID( 1, 0 );
266             rc = dw->EventLoop();
267             view->CloseWindow( dw );
268 
269             if ( rc != 0 ) {
270               view->CloseWindow( fw );
271               file = "";
272               done = true;
273               filesel = false;
274             }
275           } else {
276             view->CloseWindow( fw );
277             file.assign( "" );
278             done = true;
279             filesel = false;
280           }
281         }
282 
283       } while ( !done );
284     }
285   } while ( filesel );
286 
287   return file;
288 }
289 
290 ////////////////////////////////////////////////////////////////////////
291 // NAME       : Game::Save
292 // DESCRIPTION: Save the current game to a file.
293 // PARAMETERS : filename - data file name; if NULL pops up a file
294 //                         selection window
295 // RETURNS    : 0 on successful write, non-zero otherwise
296 ////////////////////////////////////////////////////////////////////////
297 
Save(const char * filename)298 int Game::Save( const char *filename ) {
299   string fname( CreateSaveFileName( filename ) );
300   if ( fname.length() == 0 ) return -1;
301 
302   File file( fname );
303   if ( !file.Open( "wb", false ) ) {
304     new NoteWindow( MSG(MSG_ERROR), MSG(MSG_ERR_WRITE), 0, view );
305     return -1;
306   }
307 
308   // set last filename
309   last_file_name = file_part( fname );
310   unsigned int num = last_file_name.rfind( '.' );
311   last_file_name.erase( num );
312 
313   unsigned short mflags = mission->GetFlags();
314   mission->SetFlags( mflags|GI_SAVEFILE );
315   mission->Save( file );
316   file.Close();
317 
318   if ( mflags & GI_PBEM ) {
319     string msg = StringUtil::strprintf( MSG(MSG_GAME_SAVED_PBEM), fname );
320     NoteWindow *nw = new NoteWindow( MSG(MSG_GAME_SAVED), msg, 0, view );
321     nw->SetButtonID( 0, G_BUTTON_SHUTDOWN );
322     nw->SetButtonHook( 0, this );
323   }
324   return 0;
325 }
326 
327 ////////////////////////////////////////////////////////////////////////
328 // NAME       : Game::Load
329 // DESCRIPTION: Load a game from a mission file (start a new game) or
330 //              a save file (resume game).
331 // PARAMETERS : filename - data file name
332 // RETURNS    : 0 on success, -1 otherwise
333 ////////////////////////////////////////////////////////////////////////
334 
Load(const char * filename)335 int Game::Load( const char *filename ) {
336   File file( filename );
337   if ( !file.Open( "rb" ) ) return -1;
338 
339   int rc = Load( file );
340   if ( rc != -1 ) {
341     // set last filename
342     last_file_name = file_part( filename );
343     unsigned int i = last_file_name.rfind( '.' );
344     last_file_name.erase( i );
345   } else {
346     cerr << "Error loading " << filename << endl;
347   }
348 
349   return rc;
350 }
351 
352 ////////////////////////////////////////////////////////////////////////
353 // NAME       : Game::Load
354 // DESCRIPTION: Load a game from a buffer.
355 // PARAMETERS : buffer - data buffer
356 // RETURNS    : 0 on success, -1 otherwise
357 ////////////////////////////////////////////////////////////////////////
358 
Load(MemBuffer & buffer)359 int Game::Load( MemBuffer &buffer ) {
360   int rc = -1;
361 
362   mission = new Mission();
363   if ( mission->Load( buffer ) != -1 ) {
364 
365     mission->SetLocale( CFOptions.GetLanguage() );
366 
367     unsigned short flags = mission->GetFlags();
368     if ( flags & GI_SAVEFILE ) {
369       // update CFOptions
370       if ( flags & GI_CAMPAIGN ) CFOptions.SetCampaign( true );
371       if ( flags & GI_AI ) CFOptions.SetGameType( GTYPE_AI );
372       else if ( flags & GI_PBEM ) CFOptions.SetGameType( GTYPE_PBEM );
373       else if ( flags & GI_NETWORK ) CFOptions.SetGameType( GTYPE_NET_SERVER );
374     } else {
375       // if this is a new map (GI_SAVEFILE not set) GI_SKIRMISH,
376       // GI_CAMPAIGN, and GI_AI only serve as information
377       flags &= ~(GI_AI|GI_CAMPAIGN|GI_SKIRMISH);
378       flags |= ((CFOptions.IsPBEM() ? GI_PBEM : 0)|
379                (CFOptions.IsNetwork() ? GI_NETWORK : 0)|
380                (CFOptions.IsAI() ? GI_AI : 0)|
381                (CFOptions.GetCampaign() ? GI_CAMPAIGN : 0));
382       mission->SetFlags( flags );
383     }
384 
385     rc = 0;
386 
387 #ifdef DISABLE_NETWORK
388     if ( flags & GI_NETWORK ) {
389       cerr << "Error: " << PROGRAMNAME << " was compiled without networking support" << endl;
390       rc = -1;
391 
392       delete mission;
393       mission = NULL;
394     }
395 #endif
396   } else {
397     delete mission;
398     mission = NULL;
399   }
400 
401   return rc;
402 }
403 
404 ////////////////////////////////////////////////////////////////////////
405 // NAME       : Game::SwitchMap
406 // DESCRIPTION: Dump the current map and move on to another one.
407 // PARAMETERS : sequel - map file name (excluding path and suffix)
408 // RETURNS    : -1 on error, 0 otherwise
409 ////////////////////////////////////////////////////////////////////////
410 
SwitchMap(const char * sequel)411 int Game::SwitchMap( const char *sequel ) {
412   string mapname = get_levels_dir();
413   mapname.append( sequel );
414   mapname.append( ".lev" );
415 
416   mwin->GetMapView()->Disable();
417   mwin->Draw();
418   mwin->Show();
419 
420   unsigned char p1type = mission->GetPlayer(PLAYER_ONE).Type();
421   unsigned char p2type = mission->GetPlayer(PLAYER_TWO).Type();
422   unsigned char handicap = mission->GetHandicap();
423 
424   // remove current game data
425   delete shader;
426   shader = NULL;
427   delete mission;
428   mission = NULL;
429 
430   // reset variables
431   unit = NULL;
432 
433   // load new map
434   int rc = Load( mapname.c_str() );
435   if ( !rc ) {
436     mission->GetPlayer(PLAYER_ONE).SetType( p1type );
437     mission->GetPlayer(PLAYER_TWO).SetType( p2type );
438     mission->SetHandicap( handicap );
439   }
440   return rc;
441 }
442 
443 ////////////////////////////////////////////////////////////////////////
444 // NAME       : Game::StartTurn
445 // DESCRIPTION: Start a turn, i.e. draw the map to the display.
446 // PARAMETERS : -
447 // RETURNS    : GUI status
448 ////////////////////////////////////////////////////////////////////////
449 
StartTurn(void)450 GUI_Status Game::StartTurn( void ) {
451   GUI_Status rc = GUI_OK;
452   MapView *mv = mwin->GetMapView();
453   Player &player = mission->GetPlayer();
454 
455   // if music is already playing this will do nothing
456   Audio::PlayMusic( mission->GetMusic() ? mission->GetMusic() : CF_MUSIC_DEFAULT );
457 
458   view->SetFGPen( player.LightColor() );   // set player color scheme
459   view->SetBGPen( player.DarkColor() );
460 
461   if ( player.IsInteractive() ) {
462     NoteWindow *nw;
463 
464     // show turn information and greeting dialog
465     string turnmsg( MSG(MSG_TURN) );
466     turnmsg += ' ';
467     turnmsg.append( StringUtil::tostring(mission->GetTurn()) );
468 
469     nw = new NoteWindow( player.Name(), turnmsg, WIN_FONT_BIG|WIN_CENTER, view );
470     nw->EventLoop();
471     view->CloseWindow( nw );
472 
473     // in email games set up or check player passwords
474     if ( mission->GetFlags() & GI_PBEM ) {
475       if ( !player.Password() ) {
476         if ( !SetPlayerPassword( &player ) ) return GUI_RESTART;
477       } else if ( !CheckPassword( player.Name(), MSG(MSG_ENTER_PASSWORD),
478                   player.Password(), 2 ) )
479         return GUI_RESTART;
480     }
481   }
482 
483   undo.Disable();
484 
485   if ( mission->GetPhase() == TURN_START ) {
486     // replay
487     History *history = mission->GetHistory();
488     if ( history ) {
489       if ( player.IsInteractive() ) history->Replay( mwin );
490       mission->SetHistory( NULL );
491       delete history;
492     }
493 
494     // begin new turn
495     if ( mission->GetOtherPlayer(player).IsHuman() ) {
496       history = new History();
497       history->StartRecording( mission->GetUnits() );
498       mission->SetHistory( history );
499     }
500 
501     // check for victory conditions
502     if ( mission->GetFlags() & GI_GAME_OVER )
503       return ShowDebriefing( player, true );
504 
505     mission->SetPhase( TURN_IN_PROGRESS );
506     mv->SetCursorImage( IMG_CURSOR_IDLE );
507   }
508 
509   if ( !player.IsHuman() ) {
510     CheckEvents();
511     AI ai( *mission );
512     ai.Play();
513     rc = EndTurn();
514 
515 #ifndef DISABLE_NETWORK
516   } else if ( player.IsRemote() ) {
517     // show message while waiting
518     NetworkProgressWindow *pw = new NetworkProgressWindow(
519         view->Width() * 2 / 3, view->SmallFont()->Height() + 20,
520         MSG(MSG_NET_WAITING), view );
521     DynBuffer *updates = peer->Receive( pw );
522     if ( updates ) {
523       History remote;
524       remote.Load( *updates, *mission );
525 
526       CheckEvents();
527       Execute( remote );
528     } else {
529       if ( pw->Cancelled() ) {
530         // user aborted, so no error
531         return GUI_RESTART;
532       } else {
533         HandleNetworkError();
534         return GUI_OK;
535       }
536     }
537 
538     view->CloseWindow( pw );
539 
540     // make some noise so the local player know it's his turn now
541     Audio::PlaySfx( Audio::SND_GUI_ASK, 0 );
542 
543     rc = EndTurn();
544 #endif
545 
546   } else {
547 
548     mv->Enable();
549     mv->SetFlags( MV_DIRTY );
550     CheckEvents();
551     mv->UnsetFlags( MV_DIRTY );
552 
553     // set the cursor to one of the player's units
554     Point startcursor( 0, 0 );
555     for ( Unit *u = static_cast<Unit *>(mission->GetUnits().Head());
556           u; u = static_cast<Unit *>(u->Next()) ) {
557       if ( u->Owner() == &player ) {
558         startcursor = u->Position();
559         break;
560       }
561     }
562 
563     view->DisableUpdates();
564     mv->CenterOnHex( startcursor );
565     SetCursor( startcursor );
566     view->EnableUpdates();
567     view->Refresh();
568   }
569   return rc;
570 }
571 
572 ////////////////////////////////////////////////////////////////////////
573 // NAME       : Game::SetPlayerPassword
574 // DESCRIPTION: Set a password for a player in an email game.
575 // PARAMETERS : player - player to set password for
576 // RETURNS    : TRUE if password successfully set, FALSE on error
577 ////////////////////////////////////////////////////////////////////////
578 
SetPlayerPassword(Player * player) const579 bool Game::SetPlayerPassword( Player *player ) const {
580   bool pw_ok = false;
581   PasswordWindow *pwin;
582   const char *pw, *msg1 = MSG(MSG_CHOOSE_PASSWORD);
583 
584   pwin = new PasswordWindow( player->Name(), msg1,
585                              player->Password(), false, view );
586 
587   do {
588     pwin->EventLoop();
589     pw = pwin->string->String();
590 
591     // only accept if player has entered a password
592     if ( pw ) {
593       pwin->NewPassword( pw );
594       pwin->string->SetTitle( MSG(MSG_CONFIRM_PASSWORD) );
595       pwin->string->SetString( NULL );
596       pwin->Draw();
597       pwin->Show();
598       pwin->string->SetFocus();
599 
600       if ( pwin->EventLoop() == GUI_RESTART ) break;
601 
602       pw_ok = pwin->PasswordOk();
603 
604       if ( !pw_ok ) {
605         Audio::PlaySfx( Audio::SND_GUI_ERROR, 0 );
606         pwin->NewPassword( NULL );
607         pwin->string->SetTitle( msg1 );
608         pwin->string->SetString( NULL );
609         pwin->Draw();
610         pwin->Show();
611         pwin->string->SetFocus();
612       }
613     } else pwin->string->SetFocus();
614   } while ( !pw_ok );
615 
616   player->SetPassword( pw );
617   view->CloseWindow( pwin );
618 
619   return pw_ok;
620 }
621 
622 ////////////////////////////////////////////////////////////////////////
623 // NAME       : Game::CheckPassword
624 // DESCRIPTION: Request a password from the player and compare it to a
625 //              given string.
626 // PARAMETERS : title - window title
627 //              msg   - message
628 //              pw    - password to check against
629 //              retry - number of retries if player enters a wrong
630 //                      password
631 // RETURNS    : TRUE if pw and player's password match, FALSE otherwise
632 ////////////////////////////////////////////////////////////////////////
633 
CheckPassword(const char * title,const char * msg,const char * pw,short retry) const634 bool Game::CheckPassword( const char *title, const char *msg,
635                           const char *pw, short retry ) const {
636   PasswordWindow *pwin = new PasswordWindow( title, msg, pw, true, view );
637   bool pw_ok = false;
638 
639   do {
640     if ( pwin->EventLoop() == GUI_RESTART ) break;
641     pw_ok = pwin->PasswordOk();
642 
643     if ( !pw_ok ) {
644       Audio::PlaySfx( Audio::SND_GUI_ERROR, 0 );
645       if ( retry ) {
646         pwin->string->SetString( NULL, true );
647         pwin->string->SetFocus();
648       }
649     }
650   } while ( (--retry >= 0) && !pw_ok );
651 
652   view->CloseWindow( pwin );
653   return pw_ok;
654 }
655 
656 ////////////////////////////////////////////////////////////////////////
657 // NAME       : Game::CheckEvents
658 // DESCRIPTION: Check for pending game events.
659 // PARAMETERS : -
660 // RETURNS    : GUI status
661 ////////////////////////////////////////////////////////////////////////
662 
CheckEvents(void)663 GUI_Status Game::CheckEvents( void ) {
664   Event *e = static_cast<Event *>( mission->GetEvents().Head() ), *e2;
665 
666   while ( e ) {
667     e2 = static_cast<Event *>( e->Next() );
668     if ( e->Discarded() ) {
669       e->Remove();
670       delete e;
671     } else if ( e->Check() ) {
672       // event will be executed and can be taken out of the queue
673       e->Remove();
674       e->Execute( view );
675       delete e;
676 
677       // after events have been triggered, undo is no longer allowed
678       undo.Disable();
679     }
680     e = e2;
681   }
682   return GUI_OK;
683 }
684 
685 ////////////////////////////////////////////////////////////////////////
686 // NAME       : Game::ExecPreStartEvents
687 // DESCRIPTION: This method is called before the very first turn. It is
688 //              used to execute initial ETRIGGER_HANDICAP events without
689 //              affecting the turn history (this means e.g. that units
690 //              created due to handicaps to not appear in turn replays).
691 // PARAMETERS : -
692 // RETURNS    : -
693 ////////////////////////////////////////////////////////////////////////
694 
ExecPreStartEvents(void)695 void Game::ExecPreStartEvents( void ) {
696   if ( (mission->GetTime() == 0) && (mission->GetPhase() == TURN_START) ) {
697     Event *e = static_cast<Event *>( mission->GetEvents().Head() ), *e2;
698     while ( e ) {
699       e2 = static_cast<Event *>( e->Next() );
700       if ( (e->Trigger() == ETRIGGER_HANDICAP) && e->Check() ) {
701         e->Remove();
702         e->Execute( view );
703         delete e;
704       }
705       e = e2;
706     }
707   }
708 }
709 
710 ////////////////////////////////////////////////////////////////////////
711 // NAME       : Game::HaveWinner
712 // DESCRIPTION: Check whether one of the players has won completed his
713 //              mission. Notify players and quit the current game if so.
714 // PARAMETERS : -
715 // RETURNS    : true if mission is completed by any player AND both
716 //              players have been informed so, false otherwise
717 ////////////////////////////////////////////////////////////////////////
718 
HaveWinner(void)719 bool Game::HaveWinner( void ) {
720   bool quit = false;
721 
722   if ( (mission->GetPlayer(PLAYER_ONE).Success( 0 ) >= 100) ||
723        (mission->GetPlayer(PLAYER_TWO).Success( 0 ) >= 100) ) {
724     mission->SetFlags( mission->GetFlags()|GI_GAME_OVER );
725 
726     Player &p = mission->GetPlayer();
727     quit = !mission->GetOtherPlayer(p).IsHuman();
728 
729     if ( ShowDebriefing( p, quit ) != GUI_OK ) quit = true;
730   }
731   return quit;
732 }
733 
734 ////////////////////////////////////////////////////////////////////////
735 // NAME       : Game::EndTurn
736 // DESCRIPTION: End turn for the current player. Execute combat orders
737 //              and prepare everything for the next player.
738 // PARAMETERS : -
739 // RETURNS    : GUI_Status (GUI_QUIT if mission is complete)
740 ////////////////////////////////////////////////////////////////////////
741 
EndTurn(void)742 GUI_Status Game::EndTurn( void ) {
743   GUI_Status rc = GUI_OK;
744   List &battles = mission->GetBattles();
745 
746   if ( unit ) DeselectUnit();
747   mwin->GetMapView()->DisableCursor();
748   mwin->GetPanel()->Update(NULL);
749 
750   // calculate combat modifiers
751   Combat *com = static_cast<Combat *>( battles.Head() );
752   while ( com ) {
753     com->CalcModifiers( mission->GetMap() );
754     com = static_cast<Combat *>( com->Next() );
755   }
756 
757   // execute combat orders
758   while ( !battles.IsEmpty() ) {
759     Combat *com = static_cast<Combat *>(battles.RemHead());
760     ResolveBattle( com );
761     delete com;
762   }
763 
764   // destroyed units may have triggered events...
765   rc = CheckEvents();
766 
767   // check for mission completion
768   if ( !HaveWinner() ) {
769     // set new player
770     Player &p = mission->NextPlayer();
771     if ( p.ID() == PLAYER_ONE ) mission->NextTurn();
772     mission->SetPhase( TURN_START );
773 
774     // remove all destroyed units from the list,
775     // restore movement points, reset status
776     Unit *next, *u = static_cast<Unit *>(mission->GetUnits().Head());
777     while ( u ) {
778       next = static_cast<Unit *>(u->Next());
779       if ( !u->IsAlive() ) {
780         u->Remove();
781         delete u;
782       } else if ( u->Owner() != &p ) {
783         u->UnsetFlags( U_MOVED|U_ATTACKED|U_DONE|U_BUSY );
784       }
785       u = next;
786     }
787 
788     // produce crystals in mines
789     Building *b = static_cast<Building *>(mission->GetShops().Head());
790     while ( b ) {
791       if ( (b->Owner() == &p) && b->IsMine() )
792         b->SetCrystals( b->Crystals() + b->CrystalProduction() );
793       b = static_cast<Building *>(b->Next());
794     }
795 
796     // check if we're playing an email game. if so, save and exit
797     if ( mission->GetFlags() & GI_PBEM ) {
798       string filebuf( get_save_dir() );
799       filebuf.append( last_file_name );
800       filebuf.append( ".sav" );
801 
802       int err = Save( filebuf.c_str() );
803       if ( err ) {
804         NoteWindow *nw = new NoteWindow( MSG(MSG_ERROR), MSG(MSG_ERR_SAVE), WIN_CLOSE_ESC, view );
805         nw->SetButtonID( 0, GUI_RESTART );
806       }
807     } else {
808 
809 #ifndef DISABLE_NETWORK
810       if ( p.IsRemote() ) {
811         // send data to peer
812         History *history = mission->GetHistory();
813 
814         if ( history ) {
815           DynBuffer buf;
816           history->Save( buf, true );
817           if ( !peer->Send( buf ) ) {
818             HandleNetworkError();
819             return GUI_OK;
820           }
821         } else {
822           cerr << "Error: No history available in network game" << endl;
823           return GUI_ERROR;
824         }
825       }
826 #endif
827 
828       mwin->GetMapView()->Disable();
829       mwin->Draw();
830       mwin->Show();
831 
832       rc = StartTurn();
833     }
834   }
835 
836   return rc;
837 }
838 
839 ////////////////////////////////////////////////////////////////////////
840 // NAME       : Game::ResolveBattle
841 // DESCRIPTION: Calculate and display battle outcomes for all clashes
842 //              queued.
843 // PARAMETERS : com    - battle to resolve
844 //              result - optional precalculated battle results (hits
845 //                       scored by attacker/defender, NULL to calculate)
846 //
847 // RETURNS    : GUI status
848 ////////////////////////////////////////////////////////////////////////
849 
ResolveBattle(Combat * com,const Point * result)850 void Game::ResolveBattle( Combat *com, const Point *result /* = NULL */ ) {
851   Unit *att = com->GetAttacker();
852   Unit *def = com->GetDefender();
853   if ( !att->IsAlive() || !def->IsAlive() ) return;
854 
855   // calculate modifiers for combat
856   Map &map = mission->GetMap();
857   History *hist = mission->GetHistory();
858   CombatWindow *cwin = NULL;
859 
860   if ( mission->GetPlayer().IsInteractive() )
861     cwin = new CombatWindow( com, mwin, view );
862 
863   Point apos( att->Position() ), dpos( def->Position() );
864   Point hits;
865 
866   if ( result == NULL )
867     hits = com->CalcResults();
868   else
869     hits = com->CalcResults( result->x, result->y );
870 
871   // record as a combat event
872   if ( hist && !att->IsDummy() )
873     hist->RecordCombatEvent( *com, hits.y, hits.x );
874 
875   if ( !att->IsAlive() ) map.SetUnit( NULL, apos );
876   if ( !def->IsAlive() ) map.SetUnit( NULL, dpos );
877 
878   if ( cwin ) {
879     cwin->Draw();
880     cwin->Show();
881     cwin->EventLoop();
882     view->CloseWindow( cwin );
883   }
884 }
885 
886 ////////////////////////////////////////////////////////////////////////
887 // NAME       : Game::SetCursor
888 // DESCRIPTION: Set the cursor to a new hex on the map. Contrary to the
889 //              low-level function in MapView this updates the display
890 //              at the old and new position if necessary.
891 // PARAMETERS : cursor - new cursor position
892 // RETURNS    : -
893 ////////////////////////////////////////////////////////////////////////
894 
SetCursor(const Point & cursor) const895 void Game::SetCursor( const Point &cursor ) const {
896   MapView *mv = mwin->GetMapView();
897   MapObject *mobj = NULL;
898   Unit *u;
899   Rect upd;
900   Player *player = &mission->GetPlayer();
901   Map *map = &mission->GetMap();
902 
903   if ( mv->CursorEnabled() ) {
904     Point old = mv->Cursor();
905 
906     upd = mv->SetCursor( Point(-1,-1) ); // disable cursor for hex update
907     mwin->Show( upd );                   // update previous cursor position
908 
909     if ( player->Mode() == MODE_IDLE ) {
910       // if we had highlighted a unit's target we need to remove that mark
911       const Point *target;
912       u = map->GetUnit( old );
913       if ( u && (u->Owner() == player) && (target = u->Target()) ) {
914         upd = mv->UpdateHex( *target );
915         mwin->Show( upd );
916       }
917     } else if ( player->Mode() == MODE_BUSY ) mv->SetCursorImage( IMG_CURSOR_SELECT );
918   }
919 
920   if ( cursor.x != -1 ) {
921     u = map->GetUnit( cursor );
922     if ( u ) {
923       if ( u->Owner() != player ) {
924         if ( (player->Mode() == MODE_BUSY) && unit->CanHit( u ) )
925           mv->SetCursorImage( IMG_CURSOR_ATTACK );
926       } else if ( player->Mode() == MODE_IDLE ) {
927         // if it's a unit of the active player, highlight its target if it has one
928         const Point *target = u->Target();
929         if ( target ) {
930           Point p = mv->Hex2Pixel( *target );
931           mv->DrawTerrain( IMG_CURSOR_HIGHLIGHT, mwin, p.x, p.y, *mwin);
932           mwin->Show( Rect(p.x, p.y, mv->TileWidth(), mv->TileHeight()) );
933         }
934       }
935     }
936 
937     mobj = map->GetMapObject( cursor );
938   }
939   upd = mv->SetCursor( cursor );
940   mwin->GetPanel()->Update( mobj );
941   mwin->Show( upd );
942 }
943 
944 
945 ////////////////////////////////////////////////////////////////////////
946 // NAME       : Game::MoveUnit
947 // DESCRIPTION: Move a unit to another hex.
948 // PARAMETERS : u  - unit to be moved
949 //              hx - destination hex x
950 //              hy - destination hex y
951 // RETURNS    : the unit if it's still available, or NULL if it
952 //              cannot be selected this turn (read: deselect it)
953 ////////////////////////////////////////////////////////////////////////
954 
MoveUnit(Unit * u,const Point & dest)955 Unit *Game::MoveUnit( Unit *u, const Point &dest ) {
956   if ( u->Position() != dest ) {
957     MapView *mv = mwin->GetMapView();
958     Map *map = mv->GetMap();
959     const Point &pos = u->Position();
960 
961     if ( mission->GetPlayer().IsHuman() ) {
962       if ( shader->GetStep(dest) == -1 ) return u;
963 
964       // if we're moving a transport out of a shop/transport check
965       // whether the player wants to take other units with him
966       if ( u->IsTransport() && u->IsSheltered() ) {
967         MapObject *pobj = map->GetMapObject( pos );
968         UnitContainer *parent;
969         if ( pobj->IsUnit() ) parent = static_cast<Transport *>(pobj);
970         else parent = static_cast<Building *>(pobj);
971         UnitLoadWindow *ulw = new UnitLoadWindow(
972             *static_cast<Transport *>(u), *parent,
973             *map->GetUnitSet(), *map->GetTerrainSet(),
974             mission->GetHistory(), view );
975 
976         bool aborted = false;
977         if ( ulw->Opened() ) aborted = (ulw->EventLoop() == GUI_CLOSE);
978         view->CloseWindow( ulw );
979 
980         if ( aborted ) {
981           DeselectUnit();
982           ContainerContent( parent );
983           return u;
984         }
985       }
986     }
987 
988     Path path( map );
989     if ( path.Find( u, pos, dest ) == -1 ) {
990       cerr << "Internal error: FindPath() failed!" << endl;
991       return u;
992     }
993 
994     SoundEffect *sfx = u->MoveSound();
995     if ( sfx ) sfx->Play( Audio::SFX_LOOP );
996 
997     undo.Register( u );
998     RemoveUnit( u );
999 
1000     short step, n = 0;
1001     while ( (step = path.GetStep( u->Position() )) != -1) {
1002       MoveUnit( u, (Direction)step, (n < 2) );
1003       ++n;
1004     }
1005 
1006     if ( sfx ) sfx->Stop();
1007 
1008     EndMovement( u );
1009 
1010     mwin->Show( mv->UpdateHex( u->Position() ) );
1011 
1012     if ( !u->IsReady() ) {
1013       if ( u == unit ) DeselectUnit();
1014       CheckEvents();
1015       return NULL;
1016     }
1017 
1018     shader->ShadeMap( u );
1019     if ( mv->CursorEnabled() ) mwin->GetPanel()->Update( u );
1020     mwin->Draw();
1021     mwin->Show();
1022     CheckEvents();
1023   }
1024   return u;
1025 }
1026 
1027 ////////////////////////////////////////////////////////////////////////
1028 // NAME       : Game::MoveUnit
1029 // DESCRIPTION: Move a unit one hex in a given direction.
1030 // PARAMETERS : u     - unit to be moved
1031 //              dir   - direction
1032 //              blink - if TRUE make target cursor blink (default is
1033 //                      FALSE)
1034 // RETURNS    : 0 on success, -1 on error
1035 ////////////////////////////////////////////////////////////////////////
1036 
MoveUnit(Unit * u,Direction dir,bool blink)1037 int Game::MoveUnit( Unit *u, Direction dir, bool blink /* = false */ ) {
1038   MapView *mv = mwin->GetMapView();
1039   Map *map = mv->GetMap();
1040   const Point &pos = u->Position();
1041   Point posnew;
1042   if ( map->Dir2Hex( pos, dir, posnew ) ) return -1;
1043 
1044   u->Face( dir );
1045 
1046   if ( mv->Enabled() &&
1047        (mv->HexVisible(pos) || mv->HexVisible(posnew)) )
1048     mwin->MoveHex( u->Image(), mission->GetUnitSet(),
1049                    pos, posnew, ANIM_SPEED_UNIT, blink );
1050 
1051   u->SetPosition( posnew.x, posnew.y );
1052   if ( !u->IsDummy() ) {
1053     History *h = mission->GetHistory();
1054     if ( h ) h->RecordMoveEvent( *u, dir );
1055   }
1056   return 0;
1057 }
1058 
1059 ////////////////////////////////////////////////////////////////////////
1060 // NAME       : Game::EndMovement
1061 // DESCRIPTION: Finalize a unit move.
1062 // PARAMETERS : u - unit to be moved
1063 // RETURNS    : -
1064 ////////////////////////////////////////////////////////////////////////
1065 
EndMovement(Unit * u) const1066 void Game::EndMovement( Unit *u ) const {
1067   Map &map = mission->GetMap();
1068 
1069   u->SetFlags( U_MOVED );
1070 
1071   const Point &pos = u->Position();
1072   short oldhex = map.HexTypeID( pos );
1073   int conquer = map.SetUnit( u, pos );
1074 
1075   if ( conquer == 1 ) {              // a building was conquered
1076     History *h = mission->GetHistory();
1077     if ( h ) h->RecordTileEvent( map.HexTypeID( pos ), oldhex, pos.x, pos.y );
1078   }
1079 
1080   if ( u->IsSlow() || !UnitTargets( u ) )
1081     u->SetFlags( U_DONE );
1082 }
1083 
1084 ////////////////////////////////////////////////////////////////////////
1085 // NAME       : Game::RemoveUnit
1086 // DESCRIPTION: Remove a unit from the map.
1087 // PARAMETERS : u - unit to be removed
1088 // RETURNS    : -
1089 ////////////////////////////////////////////////////////////////////////
1090 
RemoveUnit(Unit * u)1091 void Game::RemoveUnit( Unit *u ) {
1092   Map &map = mission->GetMap();
1093 
1094   if ( !u->IsSheltered() ) {
1095     map.SetUnit( NULL, u->Position() );
1096     mwin->GetMapView()->UpdateHex( u->Position() );
1097   } else {
1098     MapObject *o = map.GetMapObject( u->Position() );
1099     if ( o ) {
1100       if ( o->IsUnit() ) static_cast<Transport *>( o )->RemoveUnit( u );
1101       else static_cast<Building *>(o)->RemoveUnit( u );
1102     }
1103   }
1104 }
1105 
1106 ////////////////////////////////////////////////////////////////////////
1107 // NAME       : Game::UnitTargets
1108 // DESCRIPTION: Find out whether the unit still has things to do, i.e.
1109 //              enemies in range, mines to clear etc.
1110 // PARAMETERS : u - unit to check for
1111 // RETURNS    : TRUE if there are options left, FALSE otherwise
1112 ////////////////////////////////////////////////////////////////////////
1113 
UnitTargets(Unit * u) const1114 bool Game::UnitTargets( Unit *u ) const {
1115   bool rc = false;
1116   Unit *tg = static_cast<Unit *>( mission->GetUnits().Head() );
1117 
1118   while ( tg && !rc ) {
1119     rc = u->CanHit( tg );
1120     tg = static_cast<Unit *>( tg->Next() );
1121   }
1122 
1123   if ( !rc ) rc = MinesweeperTargets( u );
1124 
1125   return rc;
1126 }
1127 
1128 ////////////////////////////////////////////////////////////////////////
1129 // NAME       : Game::MinesweeperTargets
1130 // DESCRIPTION: If the unit is a minesweeper check if there are any
1131 //              mines to be cleared.
1132 // PARAMETERS : u - unit to check for
1133 // RETURNS    : TRUE if there are options left, FALSE otherwise
1134 ////////////////////////////////////////////////////////////////////////
1135 
MinesweeperTargets(Unit * u) const1136 bool Game::MinesweeperTargets( Unit *u ) const {
1137   bool rc = false;
1138 
1139   if ( u->IsMinesweeper() && u->IsReady() ) {
1140     Map *map = &mission->GetMap();
1141     Point adj[6];
1142 
1143     map->GetNeighbors( u->Position(), adj );
1144     for ( int i = NORTH; (i <= NORTHWEST) && !rc; ++i ) {
1145       if ( adj[i].x != -1 ) {
1146         Unit *m = map->GetUnit( adj[i] );
1147         if ( m && m->IsMine() ) {
1148           // you can only remove an enemy mine if no other enemy unit
1149           // sits next to it
1150           rc = true;
1151 
1152           if ( m->Owner() != u->Owner() ) {
1153             Point madj[6];
1154             map->GetNeighbors( m->Position(), madj );
1155 
1156             for ( int j = NORTH; (j <= NORTHWEST) && rc; ++j ) {
1157               if ( madj[j].x != -1 ) {
1158                 Unit *e = map->GetUnit( madj[j] );
1159                 if ( e && (e->Owner() != u->Owner()) && !e->IsMine() ) rc = false;
1160               }
1161             }
1162           }
1163         }
1164       }
1165     }
1166   }
1167   return rc;
1168 }
1169 
1170 ////////////////////////////////////////////////////////////////////////
1171 // NAME       : Game::SelectUnit
1172 // DESCRIPTION: Select a unit to move/attack.
1173 // PARAMETERS : u - unit to be selected
1174 // RETURNS    : -
1175 ////////////////////////////////////////////////////////////////////////
1176 
SelectUnit(Unit * u)1177 void Game::SelectUnit( Unit *u ) {
1178   if ( u->IsReady() ) {
1179     MapView *mv = mwin->GetMapView();
1180 
1181     if ( unit ) DeselectUnit( false );
1182 
1183     unit = u;
1184     mission->GetPlayer().SetMode( MODE_BUSY );
1185 
1186     if ( mv->Enabled() ) {
1187       Audio::PlaySfx( Audio::SND_GAM_SELECT, 0 );
1188       mv->EnableFog();
1189       shader->ShadeMap( unit );
1190       mwin->Draw();
1191       mwin->Show();
1192 
1193       mv->SetCursorImage( IMG_CURSOR_SELECT );
1194       SetCursor( u->Position() );
1195     }
1196   }
1197 }
1198 
1199 ////////////////////////////////////////////////////////////////////////
1200 // NAME       : Game::DeselectUnit
1201 // DESCRIPTION: Deselect the currently active unit.
1202 // PARAMETERS : update - whether to update the display or not (default
1203 //                       value is "true")
1204 // RETURNS    : -
1205 ////////////////////////////////////////////////////////////////////////
1206 
DeselectUnit(bool display)1207 void Game::DeselectUnit( bool display /* = true */ ) {
1208   MapView *mv = mwin->GetMapView();
1209   mission->GetPlayer().SetMode( MODE_IDLE );
1210 
1211   if ( mv->Enabled() ) {
1212     mv->SetCursorImage( IMG_CURSOR_IDLE );
1213 
1214     mv->DisableFog();
1215     if ( display ) {
1216       mwin->Draw();
1217       mwin->Show();
1218 
1219       SetCursor( unit->Position() );
1220     }
1221   }
1222   unit = NULL;
1223 }
1224 
1225 ////////////////////////////////////////////////////////////////////////
1226 // NAME       : Game::SelectNextUnit
1227 // DESCRIPTION: Select the current player's next available unit.
1228 // PARAMETERS : -
1229 // RETURNS    : -
1230 ////////////////////////////////////////////////////////////////////////
1231 
SelectNextUnit(void)1232 void Game::SelectNextUnit( void ) {
1233   List &units = mission->GetUnits();
1234   Unit *start, *u;
1235 
1236   if ( unit ) start = unit;
1237   else start = static_cast<Unit *>( units.Head() );
1238 
1239   u = static_cast<Unit *>( units.NextNode(start) );
1240 
1241   while ( u && (u != start) ) {
1242 
1243     if ( (u->Owner() == &mission->GetPlayer()) && u->IsReady() && !u->IsSheltered() ) {
1244       // we ignore SHELTERED units because it wouldn't be obvious which
1245       // of the units inside the transport was selected
1246       SelectUnit( u );
1247       break;
1248     }
1249 
1250     u = static_cast<Unit *>( units.NextNode(u) );
1251   }
1252 }
1253 
1254 ////////////////////////////////////////////////////////////////////////
1255 // NAME       : Game::EnterSpecialMode
1256 // DESCRIPTION: Activate one of the special game modes (for pioneers,
1257 //              mine-sweepers, or depot builders)
1258 // PARAMETERS : mode - mode to enter (see player.h for definitions)
1259 // RETURNS    : -
1260 ////////////////////////////////////////////////////////////////////////
1261 
EnterSpecialMode(unsigned char mode)1262 void Game::EnterSpecialMode( unsigned char mode ) {
1263   MapView *mv = mwin->GetMapView();
1264 
1265   mission->GetPlayer().SetMode( mode );
1266 
1267   if ( mv->Enabled() ) {
1268     MinesweeperShader mss( &mission->GetMap(), mission->GetUnits(), mv->GetFogBuffer() );
1269 
1270     mv->EnableFog();
1271     mss.ShadeMap( unit );
1272     mwin->Draw();
1273     mwin->Show();
1274 
1275     mv->SetCursorImage( IMG_CURSOR_SPECIAL );
1276     SetCursor( unit->Position() );
1277   }
1278 }
1279 
1280 ////////////////////////////////////////////////////////////////////////
1281 // NAME       : Game::ClearMine
1282 // DESCRIPTION: Try to move a mine from the map into an adjacent mine
1283 //              sweeper unit.
1284 // PARAMETERS : sweeper - mine sweeper unit (currently selected unit)
1285 //              mine    - mine to be cleared
1286 // RETURNS    : -
1287 ////////////////////////////////////////////////////////////////////////
1288 
ClearMine(Transport * sweeper,Unit * mine)1289 void Game::ClearMine( Transport *sweeper, Unit *mine ) {
1290   const Point &spos = sweeper->Position();
1291   bool allow;
1292 
1293   if ( mine->Owner() != sweeper->Owner() ) {
1294     Player *mowner = mine->Owner();
1295     mine->SetOwner( sweeper->Owner() );
1296     allow = sweeper->Allow( mine );
1297     if ( !allow ) mine->SetOwner( mowner );
1298   } else allow = sweeper->Allow( mine );
1299 
1300   if ( allow ) {
1301     MoveUnit( mine, spos );
1302 
1303     if ( !MinesweeperTargets(sweeper) ) {
1304       sweeper->SetFlags( U_DONE );
1305       DeselectUnit();
1306     }
1307 
1308     undo.Disable();
1309 
1310   } else new NoteWindow( MSG(MSG_ERROR), MSG(MSG_ERR_SWEEPER_FULL),
1311                          WIN_CLOSE_ESC, view );
1312 }
1313 
1314 ////////////////////////////////////////////////////////////////////////
1315 // NAME       : Game::Undo
1316 // DESCRIPTION: Undo the last move command the player issued.
1317 // PARAMETERS : -
1318 // RETURNS    : -
1319 ////////////////////////////////////////////////////////////////////////
1320 
Undo(void)1321 void Game::Undo( void ) {
1322   if ( unit ) DeselectUnit();
1323 
1324   Map &map = mission->GetMap();
1325   Unit *u = undo.GetUnit();
1326 
1327   RemoveUnit( u );
1328   u->Face( undo.GetDirection() );
1329   u->UnsetFlags( U_DONE|U_MOVED );
1330 
1331   // if the unit is a transport and came out of another
1332   // container we also need to reset the carried units' flags
1333   if ( u->IsTransport() && map.GetMapObject( undo.GetPosition() ) ) {
1334     Transport *t = static_cast<Transport *>(u);
1335     for ( int i = 0; i < t->UnitCount(); ++i )
1336       t->GetUnit( i )->UnsetFlags( U_DONE|U_MOVED );
1337   }
1338 
1339   map.SetUnit( u, undo.GetPosition() );
1340   mwin->GetMapView()->UpdateHex( undo.GetPosition() );
1341   mwin->Show();
1342 
1343   if ( mission->GetHistory() )
1344     mission->GetHistory()->UndoMove( *u );
1345 
1346   undo.Disable();
1347 }
1348 
1349 ////////////////////////////////////////////////////////////////////////
1350 // NAME       : Game::HandleEvent
1351 // DESCRIPTION: Handle key and mouse button events special to the map
1352 //              window.
1353 // PARAMETERS : event - event received by the event handler
1354 // RETURNS    : GUI status
1355 ////////////////////////////////////////////////////////////////////////
1356 
HandleEvent(const SDL_Event & event)1357 GUI_Status Game::HandleEvent( const SDL_Event &event ) {
1358   GUI_Status rc = GUI_OK;
1359   MapView *mv = mwin->GetMapView();
1360   Map &map = mission->GetMap();
1361 
1362   // check for keyboard commands
1363   if ( event.type == SDL_KEYDOWN ) {
1364     // some SDL ports return illegal key values
1365     short key = event.key.keysym.sym & 0x1ff;
1366     const SDLKey *keymap = CFOptions.GetKeyBindings();
1367 
1368     // map user- and locale-defined keys to standard ones
1369     if ((key > SDLK_FIRST) && (key < SDLK_LAST)) {
1370       // KEYBIND_MINIMIZE is a global binding that is not specific
1371       // to the Game class, so it isn't handled here
1372 
1373       // user-defined keys first
1374       for ( int i = 0; i < KEYBIND_COUNT; ++i ) {
1375         if ( key == keymap[i] ) {
1376           key = -i;
1377           break;
1378         }
1379       }
1380 
1381       // now check locale-defined keys
1382       if ( key >= 0 ) {
1383         if ( key == keys[G_KEY_INFO] ) key = -KEYBIND_UNIT_INFO;
1384         else if ( key == keys[G_KEY_CONTENT] ) key = -KEYBIND_UNIT_CONTENT;
1385         else if ( key == keys[G_KEY_SWEEP] ) key = -KEYBIND_UNIT_SWEEP;
1386         else if ( key == keys[G_KEY_END_TURN] ) key = -KEYBIND_END_TURN;
1387         else if ( key == keys[G_KEY_MAP] ) key = -KEYBIND_SHOW_MAP;
1388       }
1389     }
1390 
1391     switch ( key ) {
1392     case SDLK_KP1: case SDLK_KP2: case SDLK_KP3:
1393     case SDLK_KP4: case SDLK_KP6: case SDLK_KP7:
1394     case SDLK_KP8: case SDLK_KP9:
1395       if ( !(event.key.keysym.mod & KMOD_NUM) ) {
1396         ScrollCommand( key );
1397         break;
1398       }
1399       // fall through
1400     case SDLK_LEFT: case SDLK_RIGHT: case SDLK_UP: case SDLK_DOWN:
1401       MoveCommand( key );
1402       break;
1403     case SDLK_ESCAPE:
1404       if ( unit ) {
1405         DeselectUnit();
1406         break;
1407       }
1408       GameMenu();
1409       break;
1410 
1411     // the keys for the following commands can be modified by
1412     // user and/or locale, so we use fixed (negative so we don't
1413     // collide with regular keysyms) values which get mapped
1414     // before the switch statement
1415     case -KEYBIND_END_TURN:
1416       rc = EndTurn();
1417       break;
1418     case -KEYBIND_SHOW_MAP:
1419       new TacticalWindow( mv, *mission, view );
1420       break;
1421     case -KEYBIND_GAME_MENU:
1422       GameMenu();
1423       break;
1424     case -KEYBIND_UNIT_MENU: {
1425       Unit *u = map.GetUnit( mv->Cursor() );
1426       if ( u ) UnitMenu( u ); }
1427       break;
1428     case -KEYBIND_UNIT_CONTENT: {
1429       MapObject *mo = map.GetMapObject( mv->Cursor() );
1430       if ( mo ) {
1431         if ( mo->IsShop() )
1432           ContainerContent( static_cast<UnitContainer *>(
1433                             static_cast<Building *>(mo)) );
1434         else if ( static_cast<Unit *>(mo)->IsTransport() )
1435           ContainerContent( static_cast<UnitContainer *>(
1436                             static_cast<Transport *>(mo)) );
1437       }}
1438       break;
1439     case -KEYBIND_UNIT_INFO: {
1440       Unit *u = map.GetUnit( mv->Cursor() );
1441       if ( u ) UnitInfo( u ); }
1442       break;
1443     case -KEYBIND_UNIT_NEXT:
1444       SelectNextUnit();
1445       break;
1446     case -KEYBIND_UNIT_SELECT:
1447       // select the unit underneath the cursor
1448       SelectCommand( mv->Cursor() );
1449       break;
1450     case -KEYBIND_UNIT_UNDO:
1451       if ( undo.GetUnit() != NULL) Undo();
1452       break;
1453     case -KEYBIND_UNIT_SWEEP:
1454       if ( unit && MinesweeperTargets( unit ) )
1455         EnterSpecialMode( MODE_SWEEP );
1456       break;
1457     default:
1458       if ( key == keys[G_KEY_QUIT] ) Quit();
1459       break;
1460     }
1461 
1462   } else if ( event.type == SDL_MOUSEBUTTONDOWN ) {
1463     Point pos;
1464     if ( event.button.button == SDL_BUTTON_LEFT ) {
1465       if ( !mv->Pixel2Hex( event.button.x - mwin->x, event.button.y - mwin->y, pos ) ) {
1466         HandleLMB( pos );
1467       }
1468     } else if ( !mv->Pixel2Hex( event.button.x - mwin->x, event.button.y - mwin->y, pos ) ) {
1469       Unit *u = map.GetUnit( pos );
1470       if ( event.button.button == SDL_BUTTON_RIGHT ) {
1471         if ( u ) UnitMenu( u );
1472         else GameMenu();
1473       } else {    // middle mouse button
1474         if ( u ) {
1475           if ( u->IsTransport() ) ContainerContent( static_cast<Transport *>(u) );
1476         } else if ( map.GetBuilding( pos ) )
1477           ContainerContent( map.GetBuilding( pos ) );
1478       }
1479     } else GameMenu();
1480   }
1481 
1482   return rc;
1483 }
1484 
1485 ////////////////////////////////////////////////////////////////////////
1486 // NAME       : Game::MoveCommand
1487 // DESCRIPTION: Got a move command from the user. See what he wants to
1488 //              do. Move the cursor or a selected unit.
1489 // PARAMETERS : key - the key code used to give the order
1490 // RETURNS    : -
1491 ////////////////////////////////////////////////////////////////////////
1492 
MoveCommand(int key)1493 void Game::MoveCommand( int key ) {
1494   Direction dir;
1495 
1496   switch ( key ) {
1497   case SDLK_KP1:  dir = SOUTHWEST; break;
1498   case SDLK_DOWN:
1499   case SDLK_KP2:  dir = SOUTH;     break;
1500   case SDLK_KP3:  dir = SOUTHEAST; break;
1501   case SDLK_KP7:  dir = NORTHWEST; break;
1502   case SDLK_UP:
1503   case SDLK_KP8:  dir = NORTH;     break;
1504   case SDLK_KP9:  dir = NORTHEAST; break;
1505   case SDLK_LEFT:
1506   case SDLK_KP4:  dir = WEST;      break;
1507   case SDLK_RIGHT:
1508   case SDLK_KP6:  dir = EAST;      break;
1509   default: return;
1510   }
1511 
1512   Point cursor = mwin->MoveCursor( dir );
1513   if ( cursor != mwin->GetMapView()->Cursor() ) SetCursor( cursor );
1514 }
1515 
1516 ////////////////////////////////////////////////////////////////////////
1517 // NAME       : Game::SelectCommand
1518 // DESCRIPTION: Got a select command from the user. See what he wants to
1519 //              do. Select/deselect a unit, enter a building, or attack
1520 //              an enemy unit.
1521 // PARAMETERS : hex - selected hex
1522 // RETURNS    : -
1523 ////////////////////////////////////////////////////////////////////////
1524 
SelectCommand(const Point & hex)1525 void Game::SelectCommand( const Point &hex ) {
1526   MapView *mv = mwin->GetMapView();
1527   Map *map = mv->GetMap();
1528   Point cursor = mv->Cursor();
1529   Player &p = mission->GetPlayer();
1530 
1531   Unit *u = map->GetUnit( hex );
1532 
1533   switch ( p.Mode() ) {
1534   case MODE_BUSY:
1535     if ( unit->Position() == hex ) DeselectUnit();
1536     else if ( u ) {
1537       if ( u->Owner() == &p ) {
1538         if ( shader->GetStep(hex) != -1 )
1539           MoveUnit( unit, hex );    // try to move into transport
1540         else SelectUnit( u );
1541       } else if ( unit->CanHit( u ) ) {   // attack the unit
1542         mission->RegisterBattle( unit, u );
1543         DeselectUnit();
1544         mwin->FlashUnit( u->Position(), 2 );
1545         undo.Disable();
1546       }
1547     } else MoveUnit( unit, hex );  // try to move there
1548     break;
1549 
1550   case MODE_IDLE:
1551     if ( u && (u->Owner() == &p) ) SelectUnit( u );
1552     else {
1553       Building *b;
1554 
1555       if ( map->IsShop( hex ) && (b = map->GetBuilding( hex )) )
1556         ContainerContent( b );
1557     }
1558     break;
1559 
1560   case MODE_SWEEP:
1561     if ( (shader->GetStep( hex ) != -1) &&
1562          (cursor != unit->Position()) )
1563       ClearMine( static_cast<Transport *>(unit), u );
1564     else DeselectUnit();
1565   }
1566 }
1567 
1568 ////////////////////////////////////////////////////////////////////////
1569 // NAME       : Game::ScrollCommand
1570 // DESCRIPTION: Scroll the map display in the given direction.
1571 // PARAMETERS : key - the key code used to give the order
1572 // RETURNS    : -
1573 ////////////////////////////////////////////////////////////////////////
1574 
ScrollCommand(int key)1575 void Game::ScrollCommand( int key ) {
1576   MapView *mv = mwin->GetMapView();
1577   Point pos = mv->Cursor();
1578   int sw = mv->Width() * 3 / 4 / mv->TileWidth();
1579   int sh = mv->Height() * 3 / 4 / mv->TileHeight();
1580 
1581   switch ( key ) {
1582   case SDLK_KP1: pos.x -= sw; pos.y += sh; break;
1583   case SDLK_KP2:              pos.y += sh; break;
1584   case SDLK_KP3: pos.x += sw; pos.y += sh; break;
1585   case SDLK_KP4: pos.x -= sw;                break;
1586   case SDLK_KP6: pos.x += sw;                break;
1587   case SDLK_KP7: pos.x -= sw; pos.y -= sh; break;
1588   case SDLK_KP8:              pos.y -= sh; break;
1589   case SDLK_KP9: pos.x += sw; pos.y -= sh; break;
1590   default: return;
1591   }
1592 
1593   const Map *map = mv->GetMap();
1594   if ( pos.x < 0 ) pos.x = 0;
1595   else if ( pos.x >= map->Width() ) pos.x = map->Width() - 1;
1596   if ( pos.y < 0 ) pos.y = 0;
1597   else if ( pos.y >= map->Height() ) pos.y = map->Height() - 1;
1598   SetCursor( pos );
1599 }
1600 
1601 ////////////////////////////////////////////////////////////////////////
1602 // NAME       : Game::HandleLMB
1603 // DESCRIPTION: React to user pressing the left mouse button.
1604 // PARAMETERS : hex - hex the user clicked on
1605 // RETURNS    : -
1606 ////////////////////////////////////////////////////////////////////////
1607 
HandleLMB(const Point & hex)1608 void Game::HandleLMB( const Point &hex ) {
1609 
1610   bool move = (hex != mwin->GetMapView()->Cursor());
1611   Unit *u = mission->GetMap().GetUnit( hex );
1612 
1613   // activate selection if the user
1614   // - clicked the same hex twice or
1615   // - clicked one of her own units and has currently no unit selected
1616   if ( !move ||
1617        (u && !unit && (u->Owner() == &mission->GetPlayer()) && u->IsReady()) )
1618     SelectCommand( hex );
1619 
1620   else if ( move ) SetCursor( hex );
1621 }
1622 
1623 ////////////////////////////////////////////////////////////////////////
1624 // NAME       : Game::ContainerContent
1625 // DESCRIPTION: Open a window to display the content of a transport or
1626 //              building.
1627 // PARAMETERS : c - container to look into
1628 // RETURNS    : -
1629 ////////////////////////////////////////////////////////////////////////
1630 
ContainerContent(UnitContainer * c)1631 void Game::ContainerContent( UnitContainer *c ) {
1632   MapObject *mo = dynamic_cast<MapObject *>(c);
1633 
1634   if ( unit ) DeselectUnit();
1635 
1636   if ( (mo->Owner() == &mission->GetPlayer()) || !mo->Owner() ) new ContainerWindow( c, view );
1637   else new NoteWindow( mo->Name(), MSG(MSG_ERR_NO_ACCESS), WIN_CLOSE_ESC, view );
1638 }
1639 
1640 ////////////////////////////////////////////////////////////////////////
1641 // NAME       : Game::UnitInfo
1642 // DESCRIPTION: Display a window with information about a unit. If the
1643 //              current player is not authorised to peek at the unit
1644 //              specifications (e.g. because it's a hostile unit) the
1645 //              information request will fail.
1646 // PARAMETERS : unit - unit to show information about
1647 // RETURNS    : -
1648 ////////////////////////////////////////////////////////////////////////
1649 
UnitInfo(Unit * unit)1650 void Game::UnitInfo( Unit *unit ) {
1651   if ( unit->Owner() == &mission->GetPlayer() )
1652     new UnitInfoWindow( unit->Type()->ID(), mission->GetMap(), view );
1653   else new NoteWindow( unit->Name(), MSG(MSG_ERR_NO_ACCESS), WIN_CLOSE_ESC, view );
1654 }
1655 
1656 ////////////////////////////////////////////////////////////////////////
1657 // NAME       : Game::ShowLevelInfo
1658 // DESCRIPTION: Display level information supplied by the creator, if
1659 //              any. If no info was supplied, say so.
1660 // PARAMETERS : -
1661 // RETURNS    : -
1662 ////////////////////////////////////////////////////////////////////////
1663 
ShowLevelInfo(void) const1664 void Game::ShowLevelInfo( void ) const {
1665   const char *msg = mission->GetInfoMsg();
1666 
1667   if ( !msg ) msg = MSG(MSG_ERR_NO_LVL_INFO);
1668 
1669   new NoteWindow( MSG(MSG_LVL_INFO), msg, WIN_CLOSE_ESC, view );
1670 }
1671 
1672 ////////////////////////////////////////////////////////////////////////
1673 // NAME       : Game::ShowBriefing
1674 // DESCRIPTION: Display a window with the mission objectives for the
1675 //              current player. If the mission creator did not supply a
1676 //              briefing, pop up an error.
1677 // PARAMETERS : -
1678 // RETURNS    : -
1679 ////////////////////////////////////////////////////////////////////////
1680 
ShowBriefing(void) const1681 void Game::ShowBriefing( void ) const {
1682   Player &p = mission->GetPlayer();
1683 
1684   if ( p.Briefing() != -1 )
1685     new MessageWindow( p.Name(), mission->GetMessage(p.Briefing()), view );
1686   else new NoteWindow( p.Name(), MSG(MSG_ERR_NO_BRIEFING), WIN_CLOSE_ESC, view );
1687 }
1688 
1689 ////////////////////////////////////////////////////////////////////////
1690 // NAME       : Game::ShowDebriefing
1691 // DESCRIPTION: When the mission is over, display a message telling
1692 //              the players whether they won or lost and optionally
1693 //              return to the main menu.
1694 // PARAMETERS : player  - player to show debriefing for
1695 //              restart - whether to return to the main menu or load
1696 //                        the next map
1697 // RETURNS    : GUI status
1698 ////////////////////////////////////////////////////////////////////////
1699 
ShowDebriefing(Player & player,bool restart)1700 GUI_Status Game::ShowDebriefing( Player &player, bool restart ) {
1701   GUI_Status rc = GUI_OK;
1702 
1703   if ( player.IsInteractive() ) {
1704     Player *winner = NULL;
1705     bool draw = false;
1706     const char *msg;
1707     Player &p1 = mission->GetPlayer(PLAYER_ONE);
1708     Player &p2 = mission->GetPlayer(PLAYER_TWO);
1709 
1710     if ( p1.Success( 0 ) >= 100 ) winner = &p1;
1711     if ( p2.Success( 0 ) >= 100 ) {
1712       if ( winner ) draw = true;
1713       else winner = &p2;
1714     }
1715 
1716     if ( draw ) msg = MSG(MSG_RESULT_DRAW);
1717     else if ( &player == winner ) msg = MSG(MSG_RESULT_VICTORY);
1718     else msg = MSG(MSG_RESULT_DEFEAT);
1719 
1720     NoteWindow *note = new NoteWindow( MSG(MSG_DEBRIEFING), msg, WIN_CENTER, view );
1721 
1722     if ( restart ) {
1723       const char *next_map = NULL;
1724       if ( mission->GetFlags() & GI_CAMPAIGN ) next_map = mission->GetSequel();
1725       if ( !next_map || draw || !winner->IsHuman() ) {
1726         note->SetButtonID( 0, G_BUTTON_SHUTDOWN );
1727         note->SetButtonHook( 0, this );
1728       } else {
1729         note->EventLoop();
1730         view->CloseWindow( note );
1731 
1732         CFOptions.Unlock( next_map );
1733         int err = SwitchMap( next_map );
1734         if ( err == -1 ) {
1735           note = new NoteWindow( MSG(MSG_ERROR), MSG(MSG_ERR_MAP_NOT_FOUND), 0, view );
1736           note->SetButtonID( 0, G_BUTTON_SHUTDOWN );
1737           note->SetButtonHook( 0, this );
1738         } else {
1739           InitWindows();
1740           rc = StartTurn();
1741         }
1742       }
1743     } else {
1744       note->EventLoop();
1745       view->CloseWindow( note );
1746     }
1747   } else if ( restart ) rc = GUI_RESTART;
1748 
1749   return rc;
1750 }
1751 
1752 ////////////////////////////////////////////////////////////////////////
1753 // NAME       : Game::Execute
1754 // DESCRIPTION: Execute the events from a recorded turn history session.
1755 //              In contrast to History::Replay() which only creates fake
1756 //              actions for display, this function directly affects the
1757 //              current state of the game, ie. all actions are treated
1758 //              exactly as if they had been triggered by the current
1759 //              player.
1760 // PARAMETERS : history - recorded session to execute
1761 // RETURNS    : -
1762 ////////////////////////////////////////////////////////////////////////
1763 
Execute(const History & history)1764 void Game::Execute( const History &history ) {
1765   const List &events = history.GetEvents();
1766 
1767   // many event types can simply be ignored;
1768   // all we care for is movement, attacks, plus (user-triggered)
1769   // unit construction and repairs
1770   HistEvent *he = static_cast<HistEvent *>( events.Head() );
1771   while ( he ) {
1772     Unit *u, *u2;
1773 
1774     if ( he->type == History::HIST_MOVE ) {
1775       u = mission->GetUnit( he->data[0] );
1776       if ( u ) {
1777         RemoveUnit( u );
1778 
1779         do {
1780           MoveUnit( u, (Direction)he->data[1] );
1781           he = static_cast<HistEvent *>( he->Next() );
1782         } while ( he && he->type == History::HIST_MOVE && he->data[0] == u->ID() );
1783 
1784         EndMovement( u );
1785         CheckEvents();
1786         continue;
1787       }
1788 
1789     } else if ( he->type == History::HIST_UNIT ) {
1790 
1791       switch ( he->data[1] ) {
1792       case History::HIST_UEVENT_CREATE:
1793         u = history.GetDummy( he->data[0] );
1794         u2 = mission->GetUnit( he->data[0] );
1795 
1796         // units can be created by players (in factories) or by events.
1797         // we only want to execute those triggered by players, since we'd
1798         // otherwise create units twice. a cleaner solution would be
1799         // to clearly indicate for each event who/what caused it, but this
1800         // little hack does its job as well: don't build the unit if
1801         // another unit with the same ID already exists
1802         if ( u && !u2 ) {
1803           mission->CreateUnit( u->Type()->ID(), *u->Owner(), u->Position(),
1804                                (Direction)u->Facing(), u->GroupSize(), u->XP() );
1805           CheckEvents();
1806         }
1807         break;
1808       case History::HIST_UEVENT_REPAIR:
1809         u = mission->GetUnit( he->data[0] );
1810         if ( u ) {
1811           UnitContainer *uc = dynamic_cast<UnitContainer *>
1812                               (mission->GetMap().GetMapObject( u->Position() ));
1813           if ( uc )
1814             uc->SetCrystals( uc->Crystals() - CRYSTALS_REPAIR );
1815 
1816           if ( mission->GetHistory() )
1817             mission->GetHistory()->RecordUnitEvent( *u, History::HIST_UEVENT_REPAIR );
1818 
1819           u->Repair();
1820         }
1821         break;
1822       default:
1823         // ignore
1824         break;
1825       }
1826 
1827     } else if ( he->type == History::HIST_ATTACK ) {
1828       u = mission->GetUnit( he->data[0] );
1829       u2 = mission->GetMap().GetUnit( Point( he->data[1], he->data[2] ) );
1830 
1831       if ( u && u2 )
1832         mission->RegisterBattle( u, u2 );
1833 
1834     } else if ( he->type == History::HIST_COMBAT ) {
1835       u = mission->GetUnit( he->data[0] );
1836       u2 = mission->GetUnit( he->data[1] );
1837 
1838       if ( u && u2 ) {
1839         Combat cmb( u, u2 );
1840         Point casualties( he->data[3], he->data[2] );
1841         ResolveBattle( &cmb, &casualties );
1842         CheckEvents();
1843       }
1844 
1845     } else if ( he->type == History::HIST_TRANSPORT_CRYSTALS ) {
1846       u = mission->GetUnit( he->data[0] );
1847 
1848       if ( u ) {
1849         UnitContainer *uc = dynamic_cast<UnitContainer *>
1850                             (mission->GetMap().GetMapObject( u->Position() ));
1851         if ( uc ) {
1852           Transport *t = static_cast<Transport*>( u );
1853           uc->SetCrystals( uc->Crystals() - he->data[1] );
1854           u2->SetFlags( U_MOVED|U_DONE );
1855           t->SetCrystals( t->Crystals() + he->data[1] );
1856         }
1857       }
1858 
1859     } else if ( he->type == History::HIST_TRANSPORT_UNIT ) {
1860       u = mission->GetUnit( he->data[0] );
1861       u2 = mission->GetUnit( he->data[1] );
1862 
1863       if ( u && u2 ) {
1864         UnitContainer *uc = dynamic_cast<UnitContainer *>
1865                             (mission->GetMap().GetMapObject( u->Position() ));
1866         if ( uc ) {
1867           Transport *t = static_cast<Transport*>( u );
1868           uc->RemoveUnit( u2 );
1869           t->InsertUnit( u2 );
1870         }
1871       }
1872     }
1873 
1874     he = static_cast<HistEvent *>( he->Next() );
1875   }
1876 
1877   // make sure the battles are not rerun at EndTurn()
1878   mission->GetBattles().Clear();
1879 }
1880 
1881 ////////////////////////////////////////////////////////////////////////
1882 // NAME       : Game::GameMenu
1883 // DESCRIPTION: Pop up a MenuWindow with general game options like
1884 //              "End Turn" or "Quit".
1885 // PARAMETERS : -
1886 // RETURNS    : -
1887 ////////////////////////////////////////////////////////////////////////
1888 
GameMenu(void)1889 void Game::GameMenu( void ) {
1890   MenuWindow *menu = new MenuWindow( PROGRAMNAME, this, view );
1891 
1892   menu->AddItem( 0, G_BUTTON_END_TURN, 0, MSG(MSG_B_END_TURN) );
1893   menu->AddItem( 0, G_BUTTON_MAP,      0, MSG(MSG_B_MAP) );
1894   menu->AddItem( 0, G_BUTTON_BRIEFING, 0, MSG(MSG_B_OBJECTIVES) );
1895   menu->AddBar( 0 );
1896   menu->AddItem( 0, G_BUTTON_LEV_INFO, 0, MSG(MSG_B_LEVEL_INFO) );
1897   menu->AddMenu( 0, 0, MSG(MSG_B_OPTIONS) );
1898   menu->AddItem( 1, G_BUTTON_GENERAL_OPTIONS, 0, MSG(MSG_B_OPT_GENERAL) );
1899   menu->AddItem( 1, G_BUTTON_LANGUAGE_OPTIONS, 0, MSG(MSG_B_OPT_LANGUAGE) );
1900   menu->AddItem( 1, G_BUTTON_VIDEO_OPTIONS, 0, MSG(MSG_B_OPT_VIDEO) );
1901 #ifndef DISABLE_SOUND
1902   menu->AddItem( 1, G_BUTTON_SOUND_OPTIONS, 0, MSG(MSG_B_OPT_AUDIO) );
1903 #endif
1904   menu->AddItem( 1, G_BUTTON_KEYBOARD_OPTIONS, 0, MSG(MSG_B_OPT_KEYBOARD) );
1905   menu->AddBar( 0 );
1906   menu->AddItem( 0, G_BUTTON_SAVE,
1907         mission->GetFlags() & GI_PBEM ? WIDGET_DISABLED : 0,
1908         MSG(MSG_B_SAVE_GAME) );
1909 
1910   if (view->IsFullScreen())
1911     menu->AddItem( 0, G_BUTTON_MINIMIZE_WINDOW, 0, MSG(MSG_OPT_KEY_MINIMIZE) );
1912   menu->AddItem( 0, G_BUTTON_ABORT,    0, MSG(MSG_B_MAIN_MENU) );
1913   menu->AddItem( 0, G_BUTTON_QUIT,     0, MSG(MSG_B_QUIT) );
1914 
1915   menu->Layout();
1916 }
1917 
1918 ////////////////////////////////////////////////////////////////////////
1919 // NAME       : Game::UnitMenu
1920 // DESCRIPTION: Pop up a MenuWindow with possible actions for a selected
1921 //              unit.
1922 // PARAMETERS : u - unit to open window for
1923 // RETURNS    : -
1924 ////////////////////////////////////////////////////////////////////////
1925 
UnitMenu(Unit * u)1926 void Game::UnitMenu( Unit *u ) {
1927 
1928   if ( (u->Owner() == &mission->GetPlayer()) &&
1929        (u->IsTransport() || u->IsMinesweeper() || (undo.GetUnit() == u)) ) {
1930     MenuWindow *menu = new MenuWindow( u->Name(), this, view );
1931 
1932     menu->AddItem( 0, G_BUTTON_UNIT_INFO, 0, MSG(MSG_B_UNIT_INFO) );
1933 
1934     if ( u->IsTransport() )
1935       menu->AddItem( 0, G_BUTTON_UNIT_CONTENT, 0, MSG(MSG_B_UNIT_CONTENT) );
1936 
1937     if ( u->IsMinesweeper() )
1938       menu->AddItem( 0, G_BUTTON_UNIT_SWEEP,
1939             (MinesweeperTargets( u ) ? 0 : WIDGET_DISABLED), MSG(MSG_B_UNIT_SWEEP) );
1940 
1941     if ( undo.GetUnit() == u )
1942       menu->AddItem( 0, G_BUTTON_UNIT_UNDO, 0, MSG(MSG_B_UNIT_UNDO) );
1943 
1944     menu->Layout();
1945     g_tmp_prv_unit = u;
1946   } else UnitInfo( u );
1947 }
1948 
1949 ////////////////////////////////////////////////////////////////////////
1950 // NAME       : Game::HandleNetworkError
1951 // DESCRIPTION: Call when a network error has been detected. Ask user
1952 //              whether current game should be saved, and shutdown
1953 //              afterwards.
1954 // PARAMETERS : -
1955 // RETURNS    : -
1956 ////////////////////////////////////////////////////////////////////////
1957 
HandleNetworkError(void)1958 void Game::HandleNetworkError( void ) {
1959   string choices( MSG(MSG_B_SAVE) );
1960   choices += '|';
1961   choices += MSG(MSG_B_CANCEL);
1962 
1963   DialogWindow *dw = new DialogWindow( MSG(MSG_ERROR),
1964       MSG(MSG_ERR_NETWORK), choices, 0, WIN_FONT_BIG|WIN_CENTER,
1965       view );
1966   dw->SetButtonHook( this );
1967   dw->SetButtonID( 0, G_BUTTON_SAVE_AND_SHUTDOWN );
1968   dw->SetButtonID( 1, G_BUTTON_SHUTDOWN );
1969 }
1970 
1971 ////////////////////////////////////////////////////////////////////////
1972 // NAME       : Game::Shutdown
1973 // DESCRIPTION: This method should be called when the current game ends,
1974 //              either because one player surrendered, or the mission
1975 //              was completed. It will not be called when quitting the
1976 //              application altogether.
1977 // PARAMETERS : -
1978 // RETURNS    : -
1979 ////////////////////////////////////////////////////////////////////////
1980 
Shutdown(void) const1981 void Game::Shutdown( void ) const {
1982   Audio::StopMusic( CF_MUSIC_FADE_TIME );
1983 }
1984 
1985 ////////////////////////////////////////////////////////////////////////
1986 // NAME       : Game::WidgetActivated
1987 // DESCRIPTION: The WidgetActivated() method gets called whenever a
1988 //              widget from the game menu or another associated window
1989 //              (e.g. password confirmation) is activated.
1990 // PARAMETERS : button - pointer to the widget that called the function
1991 //              win    - pointer to the window the widget belongs to
1992 // RETURNS    : GUI status
1993 ////////////////////////////////////////////////////////////////////////
1994 
WidgetActivated(Widget * button,Window * win)1995 GUI_Status Game::WidgetActivated( Widget *button, Window *win ) {
1996   GUI_Status rc = GUI_OK;
1997 
1998   switch ( button->ID() ) {
1999   case G_BUTTON_END_TURN:
2000     view->CloseWindow( win );
2001     rc = EndTurn();
2002     if ( rc == GUI_RESTART ) Shutdown();
2003     break;
2004   case G_BUTTON_MAP:
2005     view->CloseWindow( win );
2006     new TacticalWindow( mwin->GetMapView(), *mission, view );
2007     break;
2008   case G_BUTTON_BRIEFING:
2009     view->CloseWindow( win );
2010     ShowBriefing();
2011     break;
2012   case G_BUTTON_LEV_INFO:
2013     view->CloseWindow( win );
2014     ShowLevelInfo();
2015     break;
2016   case G_BUTTON_GENERAL_OPTIONS:
2017     static_cast<MenuWindow *>(win)->CloseParent();
2018     view->CloseWindow( win );
2019     new GeneralOptionsWindow( mwin->GetMapView(), view );
2020     break;
2021   case G_BUTTON_LANGUAGE_OPTIONS:
2022     static_cast<MenuWindow *>(win)->CloseParent();
2023     view->CloseWindow( win );
2024     new LocaleOptionsWindow( this, view );
2025     break;
2026   case G_BUTTON_KEYBOARD_OPTIONS:
2027     static_cast<MenuWindow *>(win)->CloseParent();
2028     view->CloseWindow( win );
2029     new KeyboardOptionsWindow( view );
2030     break;
2031   case G_BUTTON_VIDEO_OPTIONS:
2032     static_cast<MenuWindow *>(win)->CloseParent();
2033     view->CloseWindow( win );
2034     new VideoOptionsWindow( view );
2035     break;
2036 #ifndef DISABLE_SOUND
2037   case G_BUTTON_SOUND_OPTIONS:
2038     static_cast<MenuWindow *>(win)->CloseParent();
2039     view->CloseWindow( win );
2040     new SoundOptionsWindow( view );
2041     break;
2042 #endif
2043   case G_BUTTON_ABORT: {
2044     view->CloseWindow( win );
2045     Audio::PlaySfx( Audio::SND_GUI_ASK, 0 );
2046     string buttons;
2047     buttons.append( MSG(MSG_B_YES) );
2048     buttons += '|';
2049     buttons.append( MSG(MSG_B_NO) );
2050     DialogWindow *req = new DialogWindow( NULL, MSG(MSG_ASK_ABORT),
2051                         buttons, 1, 0, view );
2052     req->SetButtonID( 0, G_BUTTON_SHUTDOWN );
2053     req->SetButtonHook( 0, this );
2054     break; }
2055   case G_BUTTON_QUIT:
2056     view->CloseWindow( win );
2057     Quit();
2058     break;
2059   case G_BUTTON_SAVE:
2060   case G_BUTTON_SAVE_AND_SHUTDOWN:
2061     view->CloseWindow( win );
2062     Save( NULL );
2063     if ( button->ID() == G_BUTTON_SAVE )
2064       break;
2065     // otherwise fall through
2066   case G_BUTTON_SHUTDOWN:
2067     Shutdown();
2068     rc = GUI_RESTART;
2069     break;
2070 
2071   case G_BUTTON_UNIT_INFO:
2072     view->CloseWindow( win );
2073     UnitInfo( g_tmp_prv_unit );
2074     break;
2075   case G_BUTTON_UNIT_CONTENT:
2076     view->CloseWindow( win );
2077     ContainerContent( static_cast<Transport *>(g_tmp_prv_unit) );
2078     break;
2079   case G_BUTTON_UNIT_SWEEP:
2080     view->CloseWindow( win );
2081     if ( unit != g_tmp_prv_unit ) {
2082       // disable display updates so that selection doesn't shade
2083       mwin->GetMapView()->Disable();
2084       SelectUnit( g_tmp_prv_unit );
2085       mwin->GetMapView()->Enable();
2086     }
2087     EnterSpecialMode( MODE_SWEEP );
2088     break;
2089   case G_BUTTON_UNIT_UNDO:
2090     view->CloseWindow( win );
2091     Undo();
2092     break;
2093   case G_BUTTON_MINIMIZE_WINDOW:
2094     view->CloseWindow( win );
2095     SDL_WM_IconifyWindow();
2096     break;
2097   }
2098 
2099   return rc;
2100 }
2101 
2102