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