1 /*
2  *   XFrisk - The classic board game for X
3  *   Copyright (C) 1993-1999 Elan Feingold (elan@aetherworks.com)
4  *
5  *   This program is free software; you can redistribute it and/or modify
6  *   it under the terms of the GNU General Public License as published by
7  *   the Free Software Foundation; either version 2 of the License, or
8  *   (at your option) any later version.
9  *
10  *   This program is distributed in the hope that it will be useful,
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *   GNU General Public License for more details.
14  *
15  *   You should have received a copy of the GNU General Public License
16  *   along with this program; if not, write to the Free Software
17  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  *   $Id: callbacks.c,v 1.17 2000/01/17 21:53:06 tony Exp $
20  *
21  *   Used by client/AI
22  *   Notes:
23  *     should not use X, so ai can be compiled on X-less box
24  *      is included by riskgame.c, maybe fix there?
25  *      or this be the X part, and move out other messages?
26  */
27 
28 #include <X11/X.h>
29 #include <X11/Intrinsic.h>
30 #include <X11/StringDefs.h>
31 #include <X11/Shell.h>
32 
33 #include <X11/Xaw/List.h>
34 #include <X11/Xaw/Toggle.h>
35 
36 #include <stdlib.h>
37 #include <string.h>
38 #include <stdio.h>
39 #include <unistd.h>
40 
41 #include "language.h"
42 #include "network.h"
43 #include "gui-vars.h"
44 #include "gui-func.h"
45 #include "utils.h"
46 #include "callbacks.h"
47 #include "version.h"
48 #include "riskgame.h"
49 #include "client.h"
50 #include "dice.h"
51 #include "cards.h"
52 #include "game.h"
53 #include "colormap.h"
54 #include "help.h"
55 #include "colorEdit.h"
56 #include "registerPlayers.h"
57 #include "debug.h"
58 #include "viewStats.h"
59 
60 
61 /* Game globals -- move elsewhere? -- make part of riskgame object state! */
62 static Flag	fPlayingRemotely=FALSE;
63 static Flag     fForceExchange=FALSE;
64 
65 static Int32    iCountryToFortify=-1;
66 static Int32    piMsgDstPlayerID[MAX_PLAYERS+1];
67 static Flag     fWaitMission = FALSE;
68 
69 static Flag	fHaveSeenFirstPlayer;
70 
71 Int32           iIndexMD;
72 
73 Int32           iActionState=ACTION_PLACE;
74 Int32           iReply;
75 CString         pstrMsgDstCString[MAX_PLAYERS+1];
76 Int32	        iFirstPlayer;
77 Int32           iState, iCurrentPlayer;
78 Flag            fGameStarted=FALSE;
79 
80 /************************************************************************
81  *  FUNCTION: CBK_XIncomingMessage
82  *  HISTORY:
83  *     03.17.94  ESF  Created.
84  *     06.24.94  ESF  Fixed memory leak bug.
85  *  PURPOSE:
86  *  NOTES:
87  ************************************************************************/
CBK_XIncomingMessage(XtPointer pClientData,int * iSource,XtInputId * id)88 void CBK_XIncomingMessage(XtPointer pClientData, int *iSource, XtInputId *id)
89 {
90   Int32     iMessType;
91   void     *pvMess;
92 
93   UNUSED(pClientData);
94   UNUSED(id);
95 
96   (void)RISK_ReceiveMessage(*iSource, &iMessType, &pvMess);
97   CBK_IncomingMessage(iMessType, pvMess);
98   NET_DeleteMessage(iMessType, pvMess);
99 }
100 
101 
102 /************************************************************************
103  *  FUNCTION: CBK_IncomingMessage
104  *  HISTORY:
105  *     01.26.94  ESF  Created.
106  *     01.27.94  ESF  Coded MSG_MESSAGEPACKET case.
107  *     01.29.94  ESF  Changed MSG_MESSAGEPACKET to scroll correctly.
108  *     02.05.94  ESF  Added MSG_REGISTERPLAYER handling.
109  *     03.02.94  ESF  Added more army placing glue.
110  *     03.04.94  ESF  Factored out code, cleaned up.
111  *     03.05.94  ESF  Added color-coded player turn indicator call.
112  *     03.06.94  ESF  Fixed initialization of iCurrentPlayer bug.
113  *     03.16.94  ESF  Added continent bonuses.
114  *     03.17.94  ESF  Factored out X code so that this could be more general.
115  *     03.17.94  ESF  Fixed bug, player indicator broken for remote case.
116  *     03.17.94  ESF  Added fPlayingRemotely setting.
117  *     03.18.94  ESF  Completed MSG_UPDATEARMIES code.
118  *     03.28.94  ESF  Added code for MSG_ENDOFGAME and MSG_DEADPLAYER.
119  *     03.29.94  ESF  Added code for MSG_CARDPACKET.
120  *     03.29.94  ESF  Fixed bug in handling on MSG_UPDATEARMIES.
121  *     03.29.94  ESF  Added handling for MSG_REPLYPACKET.
122  *     04.11.94  ESF  Added handling for MSG_CARDPACKET.
123  *     05.12.94  ESF  Added handling for MSG_DELETEMSGDST.
124  *     05.17.94  ESF  Added handling for MSG_NETMESSAGE.
125  *     05.19.94  ESF  Added verbose message when exit occurs.
126  *     08.28.94  ESF  Added handling for MSG_POPUPREGISTERBOX.
127  *     09.01.94  ESF  Added handling for MSG_NETPOPUP.
128  *     10.29.94  ESF  Added handling for MSG_DICEROLL.
129  *     10.29.94  ESF  Added handling for MSG_MOVENOTIFY.
130  *     10.29.94  ESF  Added handling for MSG_ATTACKNOTIFY.
131  *     10.29.94  ESF  Added handling for MSG_PLACEROLL.
132  *     01.15.95  ESF  Removed handling for MSG_DELETEMSGDST.
133  *     24.08.95  JC   Added handling for MSG_MISSION.
134  *     25.08.95  JC   new game if MSG_ENDOFGAME and winner is -1.
135  *     25.08.95  JC   Start the game only after a fortification.
136  *     28.08.95  JC   Added handling for MSG_ENDOFMISSION, MSG_VICTORY.
137  *                    Now MSG_ENDOFGAME => new game
138  *  PURPOSE:
139  *  NOTES:
140  ************************************************************************/
CBK_IncomingMessage(Int32 iMessType,void * pvMess)141 void CBK_IncomingMessage(Int32 iMessType, void *pvMess)
142 {
143   switch(iMessType)
144     {
145     case MSG_NOMESSAGE:
146       /* NoOp */
147       break;
148 
149     case MSG_NETMESSAGE:
150       UTIL_DisplayComment(((MsgNetMessage *)(pvMess))->strMessage);
151       break;
152 
153     case MSG_PLACENOTIFY:
154       {
155 	/* Copy the message, since we are using it for a while */
156 	MsgPlaceNotify *msgMess =
157 	  (MsgPlaceNotify *)MEM_Alloc(sizeof(MsgPlaceNotify));
158 	memcpy((void *)msgMess, (void *)pvMess, sizeof(MsgPlaceNotify));
159 
160 	/* Do the notification, and then add a timeout for erasing it */
161 	UTIL_PlaceNotification((XtPointer)msgMess, NULL);
162 	XtAppAddTimeOut(appContext, NOTIFY_TIME,
163 			UTIL_PlaceNotification, msgMess);
164       }
165 	break;
166 
167     case MSG_MOVENOTIFY:
168       {
169 	/* Copy the message, since we are using it for a while */
170 	MsgMoveNotify *msgMess =
171 	  (MsgMoveNotify *)MEM_Alloc(sizeof(MsgMoveNotify));
172 	memcpy((void *)msgMess, (void *)pvMess, sizeof(MsgMoveNotify));
173 
174 	/* Do the notification, and then add a timeout for erasing it */
175 	UTIL_MoveNotification((XtPointer)msgMess, NULL);
176 	XtAppAddTimeOut(appContext, NOTIFY_TIME,
177 			UTIL_MoveNotification, msgMess);
178       }
179       break;
180 
181     case MSG_ATTACKNOTIFY:
182       {
183 	/* Copy the message, since we are using it for a while */
184 	MsgAttackNotify *msgMess =
185 	  (MsgAttackNotify *)MEM_Alloc(sizeof(MsgAttackNotify));
186 	memcpy((void *)msgMess, (void *)pvMess, sizeof(MsgAttackNotify));
187 
188 	/* Do the notification, and then add a timeout for erasing it */
189 	UTIL_AttackNotification((XtPointer)msgMess, NULL);
190 	XtAppAddTimeOut(appContext, NOTIFY_TIME,
191 			UTIL_AttackNotification, msgMess);
192       }
193       break;
194 
195     case MSG_DICEROLL:
196       {
197 	MsgDiceRoll *pmsgDiceRoll = (MsgDiceRoll *)pvMess;
198 
199 	/* Set the dice colors to be the correct ones */
200 	COLOR_CopyColor(COLOR_PlayerToColor(iCurrentPlayer),
201 			COLOR_DieToColor(0));
202 	COLOR_CopyColor(COLOR_PlayerToColor(pmsgDiceRoll->iDefendingPlayer),
203 			COLOR_DieToColor(1));
204 
205 	/* Erase what was there, and then display the dice */
206 	DICE_Hide();
207 	DICE_DrawDice(pmsgDiceRoll->pAttackDice, pmsgDiceRoll->pDefendDice);
208       }
209       break;
210 
211     case MSG_NETPOPUP:
212       {
213 #ifdef ENGLISH
214         (void)UTIL_PopupDialog("Message from Server",
215 			       ((MsgNetPopup *)pvMess)->strMessage, 1,
216 			       "Ok", NULL, NULL);
217 #endif
218 #ifdef FRENCH
219         (void)UTIL_PopupDialog("Message du Serveur",
220 			       ((MsgNetPopup *)pvMess)->strMessage, 1,
221 			       "Oui", NULL, NULL);
222 #endif
223       }
224       break;
225 
226     case MSG_POPUPREGISTERBOX:
227       {
228 	/* Clear any text... */
229 	UTIL_DisplayError("");
230 	UTIL_DisplayComment("");
231 
232 	REG_PopupDialog();
233       }
234       break;
235 
236     case MSG_FORCEEXCHANGECARDS:
237       {
238         if (iState != STATE_REGISTER)
239           {
240             if (((MsgForceExchangeCards *)pvMess)->iPlayer >= 0)
241               {
242 #ifdef ENGLISH
243                 UTIL_DisplayComment("You have too many cards"
244                                     " and must exchange a set.");
245 #endif
246 #ifdef FRENCH
247                 UTIL_DisplayComment("Vous avez trop de cartes"
248                                     " et devez �changer");
249 #endif
250 	        fForceExchange = fCanExchange = TRUE;
251 	        CBK_ShowCards((Widget)NULL, (XtPointer)NULL, (XtPointer)NULL);
252 
253                 iState = STATE_PLACE;
254                 UTIL_ServerEnterState(iState);
255               }
256           }
257       }
258       break;
259 
260     case MSG_REPLYPACKET:
261       iReply = ((MsgReplyPacket *)pvMess)->iReply;
262       break;
263 
264     case MSG_ENDOFMISSION:
265       {
266 	MsgEndOfMission *pmsgEndOfMission = (MsgEndOfMission *)pvMess;
267 	Int32 iWinner = pmsgEndOfMission->iWinner;
268 
269 	D_Assert(iWinner >= 0 && iWinner < MAX_PLAYERS, "Bogus Winner!");
270 
271 	/* Print out a final message */
272 	GAME_MissionAccomplied(iWinner, pmsgEndOfMission->iTyp,
273 	                                pmsgEndOfMission->iNum1,
274 	                                pmsgEndOfMission->iNum2);
275       }
276       break;
277 
278     case MSG_VICTORY:
279       {
280 	Int32 iWinner = ((MsgVictory *)pvMess)->iWinner;
281 
282 	D_Assert(iWinner >= 0 && iWinner < MAX_PLAYERS, "Bogus Winner!");
283 
284 	/* Print out a final message */
285 	GAME_Victory(iWinner);
286       }
287       break;
288 
289     case MSG_ENDOFGAME:
290       {
291 	/* Set the cursor so that the client doesn't have
292 	 * the icon of the hourglass by any chance, because
293 	 * it doesn't really make sense.
294 	 */
295 
296 	UTIL_SetCursorShape(CURSOR_PLAY);
297 
298 	/* Print out a final message */
299 	fHaveSeenFirstPlayer = FALSE;
300 	GAME_GameOverMan();
301       }
302       break;
303 
304     case MSG_MESSAGEPACKET:
305       {
306 	MsgMessagePacket *pMess = (MsgMessagePacket *)pvMess;
307         UTIL_DisplayMessage(pMess->iFrom, pMess->iTo, pMess->strMessage);
308       }
309       break;
310 
311     case MSG_TURNNOTIFY:
312       {
313 #ifdef ASSERTIONS
314 	Int32           i;
315 #endif
316 	MsgTurnNotify  *pTurn = (MsgTurnNotify *)pvMess;
317 
318 	iCurrentPlayer = pTurn->iPlayer;
319 	if(!fHaveSeenFirstPlayer) {
320 	  fHaveSeenFirstPlayer = TRUE;
321 	  iFirstPlayer = iCurrentPlayer;
322 	}
323 
324 	/* Set the color of the player color-coded indicator */
325         GUI_SetColorOfCurrentPlayer(COLOR_PlayerToColor(iCurrentPlayer));
326 
327 	if (pTurn->iClient == CLNT_GetThisClientID())
328 	  {
329 	    fPlayingRemotely = FALSE;
330 
331 	    /* Set the cursor to indicate play */
332 	    UTIL_SetCursorShape(CURSOR_PLAY);
333 
334 	    /* See if everyone has finished fortifying their territories.
335 	     * If they have not, then there is a serious problem.
336 	     */
337 
338 	    if (!fGameStarted && (iState == STATE_FORTIFY) &&
339 		(RISK_GetNumArmiesOfPlayer(iCurrentPlayer) == 0))
340 	      {
341 		fGameStarted = TRUE;
342 		iCountryToFortify = -1;
343 
344 #ifdef ASSERTIONS
345 		/* Sanity check */
346 		for (i=0;
347 		     i!=RISK_GetNumLivePlayers();
348 		     i++)
349 		  {
350 		    Int32 iPlayer = RISK_GetNthLivePlayer(i);
351 		    Int32 iFirstAttacker;
352 		    Int32 j;
353 
354 		    D_Assert(RISK_GetNumArmiesOfPlayer(iPlayer)>=0,
355 			     "Bogus number of armies.");
356 
357 		    D_Assert(RISK_GetNumArmiesOfPlayer(iPlayer) == 0,
358 			     "This player has armies and shouldn't!");
359 
360 		    /* I not entirely sure this correct. But it's at least a
361 		     * little better than before. People who have already had
362 		     * their first turn are allowed to have cards. --Pac */
363 		    for(j=0;j!=RISK_GetNumLivePlayers();++j)
364 		      if(RISK_GetNthLivePlayer(j)==iFirstPlayer)
365 			break;
366 		    D_Assert(j!=RISK_GetNumLivePlayers(),
367 			     "first player isn't alive?");
368 		    /* negative % positive = negative, unfortunately */
369 		    iFirstAttacker=j-NUM_COUNTRIES%RISK_GetNumLivePlayers();
370 		    while(iFirstAttacker<0)
371 		      iFirstAttacker+=RISK_GetNumLivePlayers();
372 		    iFirstAttacker=RISK_GetNthLivePlayer(iFirstAttacker);
373 		    D_Assert(iPlayer < iCurrentPlayer
374 			     || iPlayer >= iFirstAttacker
375 			     || RISK_GetNumCardsOfPlayer(iPlayer) == 0,
376 			     "This player has cards and shouldn't!");
377 		  }
378 #endif
379 	      }
380 
381 	    if (fGameStarted)
382 	      {
383 		/* Force the player to exchange if he or she possesses
384 		 * 5 cards or more.
385 		 */
386 
387 		if (RISK_GetNumCardsOfPlayer(iCurrentPlayer) >= 5)
388 		  {
389 #ifdef ENGLISH
390 		    UTIL_DisplayComment("You have more than five cards"
391 					" and must exchange a set.");
392 #endif
393 #ifdef FRENCH
394 		    UTIL_DisplayComment("Vous avez plus de cinq cartes"
395 					" et devez �changer");
396 #endif
397 		    fForceExchange = TRUE;
398 		    CBK_ShowCards((Widget)NULL,
399 				  (XtPointer)NULL, (XtPointer)NULL);
400 		  }
401 
402 		/* Go into placing state, give the player as many armies as
403 		 * he or she deserves by dividing the total number of
404 		 * countries owned divided by 3, for a minimum of three
405 		 * armies.
406 		 */
407 
408 		iState = STATE_PLACE;
409 		UTIL_ServerEnterState(iState);
410 
411 		/* All of the armies should have been used up */
412 		D_Assert(RISK_GetNumArmiesOfPlayer(iCurrentPlayer) == 0,
413 			 "Number of armies should be 0!");
414 
415 		GAME_SetTurnArmiesOfPlayer(iCurrentPlayer);
416 	      }
417 	    else if (iState == STATE_FORTIFY)
418 	      {
419 		UTIL_ServerEnterState(iState);
420 		if (iCountryToFortify != -1)
421 		  {
422 		    GAME_PlaceClick(iCountryToFortify, PLACE_ONE);
423 		    iCountryToFortify = -1;
424 		    UTIL_DisplayError("");
425 		  }
426 	      }
427 	    UTIL_DisplayActionCString(iState, iCurrentPlayer);
428 	  }
429 	else
430 	  {
431 	    char buf[256];
432 	    fPlayingRemotely = TRUE;
433 
434 	    /* Set the cursor to indicate waiting */
435 	    UTIL_SetCursorShape(CURSOR_WAIT);
436 
437 #ifdef ENGLISH
438 	    snprintf(buf, sizeof(buf), "%s is playing remotely...",
439 #endif
440 #ifdef FRENCH
441 	    snprintf(buf, sizeof(buf), "%s joue � distance...",
442 #endif
443 		    RISK_GetNameOfPlayer(iCurrentPlayer));
444 	    UTIL_DisplayComment(buf);
445 	  }
446       }
447       break;
448 
449     case MSG_EXIT:
450       /* Popup telling what happened */
451 #ifdef ENGLISH
452       (void)UTIL_PopupDialog("Exit Notification",
453 			     "Terminating game!  Goodbye...",
454 			     1, "Ok", NULL, NULL);
455 #endif
456 #ifdef FRENCH
457       (void)UTIL_PopupDialog("Notification d'abandon",
458 			     "Jeu termin�!  Au revoir...",
459 			     1, "Ok", NULL, NULL);
460 #endif
461       close(CLNT_GetCommLinkOfClient(CLNT_GetThisClientID()));
462       CLNT_SetCommLinkOfClient(CLNT_GetThisClientID(), -1);
463       UTIL_ExitProgram(0);
464       break;
465 
466     case MSG_MISSION:
467       {
468         Int32 iPlayer;
469 
470         if (!fWaitMission)
471           {
472             if (UTIL_PlayerIsLocal(iCurrentPlayer))
473                 iPlayer = iCurrentPlayer;
474             else if (UTIL_NumPlayersAtClient(CLNT_GetThisClientID()) == 1)
475                 iPlayer = RISK_GetNthPlayerAtClient(CLNT_GetThisClientID(), 0);
476             else
477                 iPlayer = -1;
478             if (iPlayer != -1)
479               {
480                 GAME_ShowMission(iPlayer);
481 #ifdef ENGLISH
482                 (void)UTIL_PopupDialog("Mission Notification",
483 			               "The mission is visible in the "
484 			               "message zone",
485 			               1, "Ok", NULL, NULL);
486 #endif
487 #ifdef FRENCH
488                 (void)UTIL_PopupDialog("Notification de mission",
489 			               "La mission est visible dans la "
490 			               "zone de message",
491 			               1, "Oui", NULL, NULL);
492 #endif
493               }
494             else
495                 /* Popup telling what happened */
496 #ifdef ENGLISH
497                 (void)UTIL_PopupDialog("Mission Notification",
498 			               "To view your mission, click on\n"
499 			               " mission's button",
500 			               1, "Ok", NULL, NULL);
501 #endif
502 #ifdef FRENCH
503                 (void)UTIL_PopupDialog("Notification de mission",
504 			               "Pour voir la mission, il faut \n"
505 			               "utiliser le bouton mission",
506 			               1, "Oui", NULL, NULL);
507 #endif
508           }
509       }
510       break;
511 
512     default: {
513       char buf[256];
514 #ifdef ENGLISH
515       snprintf(buf, sizeof(buf), "CALLBACKS: Unhandled message (%d)",
516 	       iMessType);
517       (void)UTIL_PopupDialog("Fatal Error", buf, 1, "Ok", NULL, NULL);
518 #endif
519 #ifdef FRENCH
520       snprintf(buf, sizeof(buf), "CALLBACKS: message non g�r�(%d)",
521 	      iMessType);
522       (void)UTIL_PopupDialog("Erreur fatale", buf, 1, "Ok", NULL, NULL);
523 #endif
524       RISK_SendMessage(CLNT_GetCommLinkOfClient(CLNT_GetThisClientID()),
525 		       MSG_DEREGISTERCLIENT, NULL);
526       UTIL_ExitProgram(-1);
527       }
528    }
529 }
530 
531 
532 /************************************************************************
533  *  FUNCTION: CBK_RefreshMap
534  *  HISTORY:
535  *     01.27.94  ESF  Created.
536  *     01.28.94  ESF  Changed to work with pixmap.
537  *  PURPOSE:
538  *  NOTES:
539  ************************************************************************/
540 void CBK_RefreshMap(Widget w, XtPointer pData, XtPointer pCalldata)
541 {
542   XExposeEvent *pExpose = (XExposeEvent *)pCalldata;
543   UNUSED(w);
544   UNUSED(pData);
545 
546   XCopyArea(hDisplay, pixMapImage, hWindow, hGC,
547 	    pExpose->x, pExpose->y, pExpose->width, pExpose->height,
548 	    pExpose->x, pExpose->y);
549 }
550 
551 
552 #define DOUBLE_CLICK_TIME  300
553 
554 /************************************************************************
555  *  FUNCTION: CBK_MapClick
556  *  HISTORY:
557  *     01.27.94  ESF  Created
558  *     02.03.94  ESF  Don't allocate colors in order, use mapping.
559  *     03.03.94  ESF  Added some game glue.
560  *     03.07.94  ESF  Made a lean, mean, fighting function.  Look in game.c.
561  *     03.17.94  ESF  Fixed remote case.
562  *     05.10.94  ESF  Fixed to do nothing unless game has started.
563  *     10.02.94  ESF  Added PLACE_ALL option.
564  *     06.09.95  JC   Remember on click in fortify mode.
565  *     12.29.96  BPK  Added multiple armies placement in fortification
566  *     01.17.00  ThH  Now GAME_PlaceClick decides about number placed during fortify
567  *  PURPOSE:
568  *  NOTES: Handle click on map
569  ************************************************************************/
570 void CBK_MapClick(Widget w, XEvent *pEvent, String *str, Cardinal *card)
571 {
572   Int32  x, y, iCountry;
573   char buf[256];
574   UNUSED(w);
575   UNUSED(str);
576   UNUSED(card);
577 
578   if (iState == STATE_REGISTER)
579     return;
580 
581   /* Find out the coordinates and time of the mouse click */
582   x = pEvent->xbutton.x;
583   y = pEvent->xbutton.y;
584 
585   /* Find out what country was clicked on */
586   iCountry = COLOR_ColorToCountry(XGetPixel(pMapImage, x, y));
587 
588   if (fPlayingRemotely)
589     {
590       if (    (UTIL_NumPlayersAtClient(CLNT_GetThisClientID()) == 1)
591            && (iState == STATE_FORTIFY) && (iCountry < NUM_COUNTRIES))
592         {
593           if (iCountryToFortify == -1)
594             {
595               iCountryToFortify = iCountry;
596               snprintf(buf, sizeof(buf), "%s %s",
597                        RISK_GetNameOfCountry(iCountryToFortify),
598                        TXT_BE_FORTIFIED);
599               UTIL_DisplayError(buf);
600               return;
601             }
602         }
603 #ifdef ENGLISH
604       snprintf(buf, sizeof(buf), "Click as you may, it's %s's turn...",
605 #endif
606 #ifdef FRENCH
607       snprintf(buf, sizeof(buf), "Jouez avec le bouton, mais c'est le tour de %s...",
608 #endif
609 	      RISK_GetNameOfPlayer(iCurrentPlayer));
610       UTIL_DisplayError(buf);
611       return;
612     }
613 
614   /* Erase previous errors */
615   UTIL_DisplayError("");
616 
617   switch (iState)
618     {
619 
620     case STATE_FORTIFY:
621       /*
622        * Changed so we can place multiple armies during fortification
623        * -- BPK
624        * -- TdH GAME_PlaceClick takes care of it now
625        * instead fall through */
626 
627     case STATE_PLACE:
628       if (pEvent->xbutton.button == Button1)
629  	GAME_PlaceClick(iCountry, PLACE_ONE);
630       else if (pEvent->xbutton.button == Button2)
631 	GAME_PlaceClick(iCountry, PLACE_ALL);
632       else if (pEvent->xbutton.button == Button3)
633  	GAME_PlaceClick(iCountry, PLACE_MULTIPLE);
634       break;
635 
636     case STATE_ATTACK:
637       GAME_AttackClick(iCountry);
638       break;
639 
640     case STATE_MOVE:
641       GAME_MoveClick(iCountry);
642       break;
643 
644     default:
645       D_Assert(FALSE, "Shouldn't be here!");
646     }
647 }
648 
649 
650 /************************************************************************
651  *  FUNCTION: CBK_Quit
652  *  HISTORY:
653  *     01.29.94  ESF  Created
654  *     04.02.94  ESF  Added confirmation.
655  *     08.28.94  ESF  Fixed to implement cleaner exit.
656  *     15.01.00  MSH  Fixed to include Cancel button in English dialog
657  *  PURPOSE:
658  *  NOTES:
659  ************************************************************************/
660 void CBK_Quit(Widget w, XtPointer pData, XtPointer call_data)
661 {
662   Int32 rep;
663   UNUSED(w);
664   UNUSED(pData);
665   UNUSED(call_data);
666 
667 #ifdef ENGLISH
668   rep = UTIL_PopupDialog("Quit", "Quit Frisk or new game?", 3, "Quit", "New", "Cancel");
669 #endif
670 #ifdef FRENCH
671   rep = UTIL_PopupDialog("Quit", "Quitter Frisk ou nouvelle partie?", 3, "Quitter", "Nouvelle", "Annuler");
672 #endif
673   switch (rep)
674     {
675     case QUERY_YES:
676       {
677         UTIL_ExitProgram(0);
678       }
679     case QUERY_NO:
680       {
681           iState = STATE_REGISTER;
682           (void)RISK_SendMessage(CLNT_GetCommLinkOfClient(CLNT_GetThisClientID()),
683 			         MSG_ENDOFGAME, NULL);
684       }
685     }
686 }
687 
688 
689 /************************************************************************
690  *  FUNCTION: CBK_ShowCards
691  *  HISTORY:
692  *     02.19.94  ESF  Created.
693  *     03.17.94  ESF  Erase the error display.
694  *     05.04.94  ESF  Fixed so that only the owner can see his or her cards.
695  *     05.10.94  ESF  Fixed to do nothing unless game has started.
696  *     05.12.94  ESF  Fixed to display correct cards.
697  *  PURPOSE:
698  *  NOTES:
699  ************************************************************************/
700 void CBK_ShowCards(Widget w, XtPointer pData, XtPointer call_data)
701 {
702   Int32 i, iPlayer;
703   UNUSED(w);
704   UNUSED(pData);
705   UNUSED(call_data);
706 
707 #ifdef CARD_DEBUG
708   static Int32 j;
709 #endif
710 
711   if (iState == STATE_REGISTER)
712     return;
713 
714   UTIL_DisplayError("");
715 
716   /* If the player clicking is the current player, show cards, otherwise,
717    * if there's more than one player at the client, issue an error, unless
718    * there's only one, in which case show his or her cards.
719    */
720 
721   if (UTIL_PlayerIsLocal(iCurrentPlayer))
722     iPlayer = iCurrentPlayer;
723   else if (UTIL_NumPlayersAtClient(CLNT_GetThisClientID()) == 1)
724     iPlayer = RISK_GetNthPlayerAtClient(CLNT_GetThisClientID(), 0);
725   else
726     {
727       iPlayer = -1;
728 #ifdef ENGLISH
729       (void)UTIL_PopupDialog("Error", "Wait until your turn!",
730 			     1, "Ok", "Cancel", NULL);
731 #endif
732 #ifdef FRENCH
733       (void)UTIL_PopupDialog("Erreur", "Il faut attendre son tour!",
734 			     1, "Ok", "Annule", NULL);
735 #endif
736     }
737 
738   /* If it's valid, display cards */
739   if (iPlayer >= 0)
740     {
741       XtPopup(wCardShell, XtGrabExclusive);
742 
743 #ifdef CARD_DEBUG
744       for (i=0; i!=MAX_CARDS; i++)
745 	{
746 	  CARD_RenderCard(j, i);
747 	  j = (++j) % NUM_CARDS;
748 	}
749 #else
750       for (i=0; i!=RISK_GetNumCardsOfPlayer(iPlayer); i++)
751 	CARDS_RenderCard(RISK_GetCardOfPlayer(iPlayer, i), i);
752 #endif
753     }
754 }
755 
756 
757 /************************************************************************
758  *  FUNCTION: CBK_CancelCards
759  *  HISTORY:
760  *     02.19.94  ESF  Created.
761  *     03.17.94  ESF  Added clearing of selections and unmappings.
762  *     04.01.94  ESF  Fixed clearing of error when player cancels.
763  *     04.11.94  ESF  Added not popping down the shell when !fForceExchange.
764  *  PURPOSE:
765  *  NOTES:
766  ************************************************************************/
767 void CBK_CancelCards(Widget w, XtPointer pData, XtPointer call_data)
768 {
769   Int32 i;
770   UNUSED(w);
771   UNUSED(pData);
772   UNUSED(call_data);
773 
774   if (fForceExchange == TRUE)
775 #ifdef ENGLISH
776     (void)UTIL_PopupDialog("Error", "You must exchange cards!", 1,
777 			   "Ok", NULL, NULL);
778 #endif
779 #ifdef FRENCH
780     (void)UTIL_PopupDialog("Erreur", "Il faut �changer des cartes!", 1,
781 			   "Ok", NULL, NULL);
782 #endif
783   else
784     {
785       UTIL_DisplayError("");
786       XtPopdown(wCardShell);
787 
788       /* Unmap the cards and clear the selections */
789       for (i=0; i!=MAX_CARDS; i++)
790 	{
791 	  XtUnmanageChild(wCardToggle[i]);
792 	  XtVaSetValues(wCardToggle[i], XtNstate, False, NULL);
793 	}
794     }
795 }
796 
797 
798 /************************************************************************
799  *  FUNCTION: CBK_ShowMission
800  *  HISTORY:
801  *     16.08.95  JC  Created.
802  *  PURPOSE:
803  *  NOTES:
804  ************************************************************************/
805 void CBK_ShowMission(Widget w, XtPointer pData, XtPointer call_data)
806 {
807   Int32 iPlayer;
808   UNUSED(w);
809   UNUSED(pData);
810   UNUSED(call_data);
811 
812   if (iState == STATE_REGISTER)
813     return;
814 
815   UTIL_DisplayError("");
816 
817   /* If the player clicking is the current player, show cards, otherwise,
818    * if there's more than one player at the client, issue an error, unless
819    * there's only one, in which case show his or her cards.
820    */
821 
822   if (UTIL_PlayerIsLocal(iCurrentPlayer))
823     iPlayer = iCurrentPlayer;
824   else if (UTIL_NumPlayersAtClient(CLNT_GetThisClientID()) == 1)
825     iPlayer = RISK_GetNthPlayerAtClient(CLNT_GetThisClientID(), 0);
826   else
827     {
828       iPlayer = -1;
829 #ifdef ENGLISH
830       (void)UTIL_PopupDialog("Error", "Wait until your turn!",
831 			     1, "Ok", NULL, NULL);
832 #endif
833 #ifdef FRENCH
834       (void)UTIL_PopupDialog("Erreur", "Il faut attendre son tour!",
835 			     1, "Ok", NULL, NULL);
836 #endif
837     }
838 
839   /* If it's valid, display mission */
840   if (iPlayer >= 0)
841     {
842       if (RISK_GetMissionTypeOfPlayer(iPlayer) == NO_MISSION)
843         {
844 #ifdef ENGLISH
845           if(UTIL_PopupDialog("Mission", "Play with a mission?",
846 			         2, "Ok", "Cancel", NULL) == QUERY_NO)
847 #endif
848 #ifdef FRENCH
849           if(UTIL_PopupDialog("Mission", "Jouer avec une mission?",
850 			         2, "Oui", "Annule", NULL) == QUERY_NO)
851 #endif
852               return;
853           fWaitMission = TRUE;
854           (void)RISK_SendSyncMessage(CLNT_GetCommLinkOfClient(CLNT_GetThisClientID()),
855 			             MSG_MISSION, NULL,
856 			             MSG_MISSION, CBK_IncomingMessage);
857           fWaitMission = FALSE;
858         }
859       GAME_ShowMission(iPlayer);
860     }
861 }
862 
863 
864 /************************************************************************
865  *  FUNCTION: CBK_Attack
866  *  HISTORY:
867  *     03.03.94  ESF  Stubbed.
868  *     03.05.94  ESF  Coded.
869  *     05.07.94  ESF  Modified to work with Distributed RiskGame Object.
870  *     05.10.94  ESF  Fixed to do nothing unless game has started.
871  *     05.19.94  ESF  Fixed bug, not refering to DiceMode.
872  *     10.15.94  ESF  Added flexibility.
873  *     01.17.95  ESF  Free pItem.
874  *  PURPOSE:
875  *  NOTES:
876  ************************************************************************/
877 void CBK_Attack(Widget w, XtPointer pData, XtPointer call_data)
878 {
879   XawListReturnStruct *pItem;
880   UNUSED(w);
881   UNUSED(pData);
882   UNUSED(call_data);
883 
884   if (iState == STATE_REGISTER)
885     return;
886 
887   /* Don't do anything if current player is not local and there is not
888    * one player at this client.
889    */
890 
891   if (UTIL_PlayerIsLocal(iCurrentPlayer) == FALSE)
892     return;
893 
894   /* I know this is silly, but hey... (We should get the data from pData?) */
895   pItem = XawListShowCurrent(wAttackList);
896   RISK_SetDiceModeOfPlayer(iCurrentPlayer, pItem->list_index);
897 
898   /* Free up memory */
899   XtFree((void *)pItem);
900 }
901 
902 
903 /************************************************************************
904  *  FUNCTION: CBK_Action
905  *  HISTORY:
906  *     03.03.94  ESF  Stubbed.
907  *     03.05.94  ESF  Coded.
908  *     03.29.94  ESF  Added player attack mode history.
909  *     05.07.94  ESF  Modified to work with Distributed RiskGame Object.
910  *     05.10.94  ESF  Fixed to do nothing unless game has started.
911  *     11.19.94  ESF  Added a few UTIL_DisplayError("")'s, where needed.
912  *     01.17.95  ESF  Free pItem.
913  *  PURPOSE:
914  *  NOTES:
915  ************************************************************************/
916 void CBK_Action(Widget w, XtPointer pData, XtPointer call_data)
917 {
918   XawListReturnStruct *pItem;
919   UNUSED(w);
920   UNUSED(pData);
921   UNUSED(call_data);
922 
923   if (iState == STATE_REGISTER)
924     return;
925 
926   /* Don't do anything if current player is not local */
927   if (!UTIL_PlayerIsLocal(iCurrentPlayer))
928     return;
929 
930   /* I know this is silly, but hey... (We should get the data from pData?) */
931   pItem = XawListShowCurrent(wActionList);
932 
933   switch (pItem->list_index)
934     {
935     /*****************/
936     case ACTION_PLACE:
937     /*****************/
938       if (iState == STATE_PLACE || iState == STATE_FORTIFY)
939 	{
940 	  UTIL_DisplayError("");
941 	  iActionState = pItem->list_index;
942 	}
943       else /* Invalid */
944 	{
945 #ifdef ENGLISH
946 	  UTIL_DisplayError("You've already placed your armies.");
947 #endif
948 #ifdef FRENCH
949 	  UTIL_DisplayError("Toutes vos arm�es sont d�j� plac�e.");
950 #endif
951 	  XawListHighlight(wActionList, ACTION_ATTACK);
952 	}
953 
954       break;
955 
956     /*******************/
957     case ACTION_ATTACK:
958     case ACTION_DOORDIE:
959     /*******************/
960       if (iState == STATE_ATTACK)
961 	{
962 	  UTIL_DisplayError("");
963 	  iActionState = pItem->list_index;
964 
965 	  /* Keep track of the type of attack selected */
966 	  RISK_SetAttackModeOfPlayer(iCurrentPlayer, pItem->list_index);
967 	}
968       else if (iState == STATE_PLACE || iState == STATE_FORTIFY)
969 	{
970 #ifdef ENGLISH
971 	  UTIL_DisplayError("You must finish placing your armies.");
972 #endif
973 #ifdef FRENCH
974 	  UTIL_DisplayError("Il faut finir de placer vos arm�es.");
975 #endif
976 	  XawListHighlight(wActionList, ACTION_PLACE);
977 	}
978       else if (iState == STATE_MOVE) /* Must not have moved yet */
979 	{
980 	  /* Keep track of the type of attack selected */
981 	  RISK_SetAttackModeOfPlayer(iCurrentPlayer, pItem->list_index);
982 
983 	  iState = STATE_ATTACK;
984 	  UTIL_DisplayError("");
985 	  UTIL_ServerEnterState(iState);
986 	  UTIL_DisplayActionCString(iState, iCurrentPlayer);
987 	}
988 
989       break;
990 
991     /****************/
992     case ACTION_MOVE:
993     /****************/
994       if (iState == STATE_MOVE)
995 	{
996 	  UTIL_DisplayError("");
997 	  iActionState = pItem->list_index;
998 	}
999       else if (iState == STATE_FORTIFY || iState == STATE_PLACE)
1000 	{
1001 #ifdef ENGLISH
1002 	  UTIL_DisplayError("You must finish placing your armies.");
1003 #endif
1004 #ifdef FRENCH
1005 	  UTIL_DisplayError("Il faut finir de placer vos arm�es.");
1006 #endif
1007 	  XawListHighlight(wActionList, ACTION_PLACE);
1008 	}
1009       else /* STATE_ATTACK */
1010 	{
1011 	  iState = STATE_MOVE;
1012 	  UTIL_DisplayError("");
1013 	  UTIL_ServerEnterState(iState);
1014 	  UTIL_DisplayActionCString(iState, iCurrentPlayer);
1015 	}
1016       break;
1017 
1018     default:
1019       D_Assert(FALSE, "Shouldn't be here -- bogus state!");
1020     }
1021 
1022   /* Free up memory */
1023   XtFree((void *)pItem);
1024 }
1025 
1026 
1027 /************************************************************************
1028  *  FUNCTION: CBK_MsgDest
1029  *  HISTORY:
1030  *     03.03.94  ESF  Stubbed.
1031  *     05.07.94  ESF  Created and modified to work with Distributed Object.
1032  *     05.19.94  ESF  Fixed a bug, wasn't refering to MsgDestList.
1033  *     01.17.95  ESF  Free pItem.
1034  *  PURPOSE:
1035  *  NOTES:
1036  ************************************************************************/
1037 void CBK_MsgDest(Widget w, XtPointer pData, XtPointer call_data)
1038 {
1039   XawListReturnStruct *pItem;
1040   Int32 iPlayer;
1041   UNUSED(w);
1042   UNUSED(pData);
1043   UNUSED(call_data);
1044 
1045   if (UTIL_PlayerIsLocal(iCurrentPlayer))
1046     iPlayer = iCurrentPlayer;
1047   else if (UTIL_NumPlayersAtClient(CLNT_GetThisClientID()) == 1)
1048     iPlayer = RISK_GetNthPlayerAtClient(CLNT_GetThisClientID(), 0);
1049   else
1050       iPlayer = -1;
1051   if (iPlayer == -1)
1052     return;
1053 
1054   /* I know this is silly, but hey... (We should get the data from pData?) */
1055   pItem = XawListShowCurrent(wMsgDestList);
1056   RISK_SetMsgDstModeOfPlayer(iPlayer, pItem->list_index);
1057 
1058   /* Free up memory */
1059   XtFree((void *)pItem);
1060 }
1061 
1062 
1063 /************************************************************************
1064  *  FUNCTION: CBK_SendMessage
1065  *  HISTORY:
1066  *     05.03.94  ESF  Created.
1067  *     05.04.94  ESF  Enhanced.
1068  *     10.02.94  ESF  Moved here and fixed a few (serious) bugs.
1069  *     01.09.95  ESF  Changed to name sender if current player on client.
1070  *     01.17.95  ESF  Free pItem.
1071  *     29.08.95  JC   strFrom -> iFrom, iTo now contain the iPlayer.
1072  *  PURPOSE:
1073  *  NOTES:
1074  ************************************************************************/
1075 void CBK_SendMessage(void)
1076 {
1077   CString                strMess;
1078   XawListReturnStruct   *pItem;
1079   MsgMessagePacket       mess;
1080 
1081   /* Get the message and destination from widgets */
1082   XtVaGetValues(wSendMsgText, XtNstring, &strMess, NULL);
1083   pItem = XawListShowCurrent(wMsgDestList);
1084 
1085   /* Who's sending the message? */
1086   if (UTIL_NumPlayersAtClient(CLNT_GetThisClientID()) == 1)
1087     mess.iFrom = RISK_GetNthPlayerAtClient(CLNT_GetThisClientID(), 0);
1088   else if (RISK_GetClientOfPlayer(iCurrentPlayer) == CLNT_GetThisClientID())
1089     mess.iFrom = iCurrentPlayer;
1090   else
1091     mess.iFrom = FROM_UNKNOW;
1092 
1093   /* Who do we send the message to? */
1094   if (pItem->list_index == -1 || pItem->list_index == 0)
1095     mess.iTo = DST_ALLPLAYERS;
1096   else
1097     {
1098       mess.iTo = piMsgDstPlayerID[pItem->list_index];
1099       D_Assert(mess.iTo!=-1, "Bogus player!");
1100     }
1101 
1102   mess.strMessage = strMess;
1103 
1104   /* Display the message locally if necessary */
1105   if (mess.iTo != DST_ALLPLAYERS &&
1106       RISK_GetClientOfPlayer(mess.iTo) != CLNT_GetThisClientID()) {
1107     UTIL_DisplayMessage(mess.iFrom, mess.iTo, mess.strMessage);
1108   }
1109   /* Send the message */
1110   (void)RISK_SendMessage(CLNT_GetCommLinkOfClient(CLNT_GetThisClientID()),
1111 			 MSG_MESSAGEPACKET, &mess);
1112 
1113   /* Erase the old message */
1114   XtVaSetValues(wSendMsgText, XtNstring, "", NULL);
1115 
1116   /* Free up memory */
1117   XtFree((void *)pItem);
1118 }
1119 
1120 
1121 /************************************************************************
1122  *  FUNCTION: CBK_CancelAttack
1123  *  HISTORY:
1124  *     03.03.94  ESF  Stubbed.
1125  *     03.04.94  ESF  Coded.
1126  *     05.10.94  ESF  Fixed to do nothing unless game has started.
1127  *  PURPOSE:
1128  *  NOTES:
1129  ************************************************************************/
1130 void CBK_CancelAttack(Widget w, XtPointer pData, XtPointer call_data)
1131 {
1132   UNUSED(w);
1133   UNUSED(pData);
1134   UNUSED(call_data);
1135 
1136   if (iState == STATE_REGISTER)
1137     return;
1138 
1139   if (fPlayingRemotely)
1140     {
1141 #ifdef ENGLISH
1142       UTIL_DisplayError("Nice try, but it isn't your turn...");
1143 #endif
1144 #ifdef FRENCH
1145       UTIL_DisplayError("Bien essay�, mais ce n'est pas votre tour...");
1146 #endif
1147       return;
1148     }
1149 
1150   UTIL_DisplayError("");
1151 
1152   if (iState == STATE_ATTACK)
1153     {
1154       if (GAME_AttackFrom() >=0)
1155 	UTIL_DarkenCountry(GAME_AttackFrom());
1156       GAME_SetAttackSrc(-1);
1157     }
1158   else if (iState == STATE_MOVE)
1159     {
1160       if (iMoveSrc >=0)
1161 	UTIL_DarkenCountry(iMoveSrc);
1162       iMoveSrc = -1;
1163     }
1164 
1165   UTIL_DisplayActionCString(iState, iCurrentPlayer);
1166 }
1167 
1168 
1169 /************************************************************************
1170  *  FUNCTION: CBK_Repeat
1171  *  HISTORY:
1172  *     03.03.94  ESF  Stubbed.
1173  *     03.06.94  ESF  Coded.
1174  *     03.07.94  ESF  Fixed state bug.
1175  *     03.08.94  ESF  Added DOORDIE recognition.
1176  *     03.16.94  ESF  Fixed bug in DOORDIE.
1177  *     04.02.94  ESF  Added server notification caching for do-or-die.
1178  *     05.10.94  ESF  Fixed to do nothing unless game has started.
1179  *     05.19.94  ESF  Added verbose message across network.
1180  *  PURPOSE:
1181  *  NOTES:
1182  ************************************************************************/
1183 void CBK_Repeat(Widget w, XtPointer pData, XtPointer call_data)
1184 {
1185   MsgNetMessage mess;
1186   char buf[256];
1187   UNUSED(w);
1188   UNUSED(pData);
1189   UNUSED(call_data);
1190 
1191   if (iState == STATE_REGISTER)
1192     return;
1193 
1194   if (fPlayingRemotely)
1195     {
1196 #ifdef ENGLISH
1197       snprintf(buf, sizeof(buf), "I'm afraid that's up to %s to decide...",
1198 #endif
1199 #ifdef FRENCH
1200       snprintf(buf, sizeof(buf),
1201 	       "Je suis effray� par ce que vous d�cidez pour %s...",
1202 #endif
1203 	      RISK_GetNameOfPlayer(iCurrentPlayer));
1204       UTIL_DisplayError(buf);
1205       return;
1206     }
1207 
1208   if (iState != STATE_ATTACK)
1209 #ifdef ENGLISH
1210     UTIL_DisplayError("You can't do any attacking at the moment.");
1211 #endif
1212 #ifdef FRENCH
1213     UTIL_DisplayError("Il n'y a rien � faire pour l'instant.");
1214 #endif
1215   else if (iActionState == ACTION_ATTACK || iActionState == ACTION_DOORDIE)
1216     {
1217       if (iLastAttackSrc == -1 || iLastAttackDst == -1)
1218 	{
1219 #ifdef ENGLISH
1220 	  UTIL_DisplayError("You can't repeat an uncomplete attack.");
1221 #endif
1222 #ifdef FRENCH
1223 	  UTIL_DisplayError("Impossible de r�p�ter une attaque incompl�te.");
1224 #endif
1225 	  return;
1226 	}
1227 
1228       /* send a verbose message */
1229 #ifdef ENGLISH
1230       snprintf(buf, sizeof(buf), "%s is attacking from %s to %s",
1231 	      RISK_GetNameOfPlayer(iCurrentPlayer),
1232 	      RISK_GetNameOfCountry(iLastAttackSrc),
1233 	      RISK_GetNameOfCountry(iLastAttackDst));
1234 #endif
1235 #ifdef FRENCH
1236       snprintf(buf, sizeof(buf), "%s attaque %s depuis %s",
1237 	      RISK_GetNameOfPlayer(iCurrentPlayer),
1238 	      RISK_GetNameOfCountry(iLastAttackDst),
1239 	      RISK_GetNameOfCountry(iLastAttackSrc));
1240 #endif
1241       mess.strMessage = buf;
1242       (void)RISK_SendMessage(CLNT_GetCommLinkOfClient(CLNT_GetThisClientID()),
1243 			     MSG_NETMESSAGE, &mess);
1244 
1245       /* Repeat the attack */
1246       GAME_Attack(iLastAttackSrc, iLastAttackDst);
1247     }
1248 }
1249 
1250 
1251 /************************************************************************
1252  *  FUNCTION: CBK_Help
1253  *  HISTORY:
1254  *     04.01.94  ESF  Coded.
1255  *  PURPOSE:
1256  *  NOTES:
1257  ************************************************************************/
1258 void CBK_Help(Widget w, XtPointer pData, XtPointer call_data)
1259 {
1260   Int32 x, y;
1261   UNUSED(w);
1262   UNUSED(pData);
1263   UNUSED(call_data);
1264 
1265   /* Center popup */
1266   UTIL_CenterShell(wHelpShell, wToplevel, &x, &y);
1267   XtVaSetValues(wHelpShell,
1268 		XtNallowShellResize, False,
1269 		XtNx, x,
1270 		XtNy, y,
1271 		XtNborderWidth, 1,
1272 #ifdef ENGLISH
1273 		XtNtitle, "Frisk Help",
1274 #endif
1275 #ifdef FRENCH
1276 		XtNtitle, "Aide de frisk",
1277 #endif
1278 		NULL);
1279 
1280   XtPopup(wHelpShell, XtGrabNone);
1281 }
1282 
1283 
1284 /************************************************************************
1285  *  FUNCTION: CBK_HelpSelectTopic
1286  *  HISTORY:
1287  *     04.01.94  ESF  Coded.
1288  *     01.17.95  ESF  Free pItem.
1289  *  PURPOSE:
1290  *  NOTES:
1291  ************************************************************************/
1292 void CBK_HelpSelectTopic(Widget w, XtPointer pData, XtPointer call_data)
1293 {
1294   XawListReturnStruct *pItem;
1295   UNUSED(w);
1296   UNUSED(pData);
1297   UNUSED(call_data);
1298 
1299   /* I know this is sill, but hey... (We should get the data from pData) */
1300   pItem = XawListShowCurrent(wHelpTopicList);
1301   HELP_IndexPopupHelp(pItem->list_index);
1302 
1303   /* Free up memory */
1304   XtFree((void *)pItem);
1305 }
1306 
1307 
1308 /************************************************************************
1309  *  FUNCTION: CBK_HelpOk
1310  *  HISTORY:
1311  *     04.01.94  ESF  Coded.
1312  *  PURPOSE:
1313  *  NOTES:
1314  ************************************************************************/
1315 void CBK_HelpOk(Widget w, XtPointer pData, XtPointer call_data)
1316 {
1317   UNUSED(w);
1318   UNUSED(pData);
1319   UNUSED(call_data);
1320   XtPopdown(wHelpShell);
1321 }
1322 
1323 
1324 /************************************************************************
1325  *  FUNCTION: CBK_ExchangeCards
1326  *  HISTORY:
1327  *     03.03.94  ESF  Stubbed.
1328  *     03.29.94  ESF  Coded.
1329  *     05.03.94  ESF  Fixed for when the player has many cards.
1330  *     02.21.95  ESF  Fixed a bug -- only current player may exchange.
1331  *  PURPOSE:
1332  *  NOTES:
1333  ************************************************************************/
1334 void CBK_ExchangeCards(Widget w, XtPointer pData, XtPointer call_data)
1335 {
1336   Int32   i, iNumCardsSelected;
1337   Flag    iCardState;
1338   Int32   piCards[3], piCardValues[3], piCardTypes[3];
1339   UNUSED(w);
1340   UNUSED(pData);
1341   UNUSED(call_data);
1342 
1343   /* If it's not the current player, then don't let the exchange
1344    * take place.  This fixes a nasty bug reported by many.
1345    */
1346 
1347   if (RISK_GetClientOfPlayer(iCurrentPlayer) != CLNT_GetThisClientID())
1348     UTIL_DisplayError(TXT_ONLY_CURRENT);
1349   /* Get the cards that have been pressed, check for validity */
1350   for (i=iNumCardsSelected=0; i!=MAX_CARDS; i++)
1351   {
1352       XtVaGetValues(wCardToggle[i], XtNstate, &iCardState, NULL);
1353       if (iCardState == True)
1354       {
1355           if (iNumCardsSelected<3)
1356               piCards[iNumCardsSelected] = i;
1357           iNumCardsSelected++;
1358       }
1359   }
1360 
1361   /* Clear the selections of the cards */
1362   for (i=0; i!=MAX_CARDS; i++)
1363     XtVaSetValues(wCardToggle[i], XtNstate, False, NULL);
1364 
1365   /* Valid number of cards selected */
1366   if (fCanExchange == FALSE)
1367     {
1368 #ifdef ENGLISH
1369       UTIL_DisplayError("You can only exchange cards at the beginning of "
1370 			"your turn.");
1371 #endif
1372 #ifdef FRENCH
1373       UTIL_DisplayError("Les cartes ne sont �changeables qu'au d�but de "
1374 			"votre tour.");
1375 #endif
1376       return;
1377     }
1378   else if (iNumCardsSelected!=3)
1379     {
1380 #ifdef ENGLISH
1381       UTIL_DisplayError("You must select three cards to exchange.");
1382 #endif
1383 #ifdef FRENCH
1384       UTIL_DisplayError("Il faut s�lectionner trois cartes pour pouvoir "
1385                         "�changer.");
1386 #endif
1387       return;
1388     }
1389 
1390   /* Substitute the indices for the card values */
1391   for (i=0; i!=3; i++)
1392     {
1393       piCardValues[i] = RISK_GetCardOfPlayer(iCurrentPlayer, piCards[i]);
1394 
1395       /* Set the type of the card */
1396       if (piCardValues[i]<NUM_COUNTRIES)
1397 	piCardTypes[i] = piCardValues[i] % 3;
1398       else
1399 	piCardTypes[i] = -1;  /* Joker */
1400     }
1401 
1402   /* Now see if the cards form a valid triple */
1403   if ((piCardTypes[0]==piCardTypes[1] &&
1404        piCardTypes[1]==piCardTypes[2] &&
1405        piCardTypes[0]==piCardTypes[2]) ||
1406       (piCardTypes[0]!=piCardTypes[1] &&
1407        piCardTypes[1]!=piCardTypes[2] &&
1408        piCardTypes[0]!=piCardTypes[2]) ||
1409       (piCardTypes[0]==-1 || piCardTypes[1]==-1 || piCardTypes[2]==-1))
1410     {
1411       /* Actually perform the exchange */
1412       GAME_ExchangeCards(piCards);
1413 
1414       /* If this is the only exchange, then pop down the window */
1415       if (RISK_GetNumCardsOfPlayer(iCurrentPlayer) <= 5)
1416 	{
1417 	  /* We're through exchanging sets of cards */
1418 	  fForceExchange = FALSE;
1419 
1420 	  XtPopdown(wCardShell);
1421 
1422 	  /* Unmap the cards and clear the selections */
1423 	  for (i=0; i!=MAX_CARDS; i++)
1424 	    XtUnmanageChild(wCardToggle[i]);
1425 	}
1426       else
1427 	{
1428 	  /* Redisplay cards after erasing and unselecting the old ones */
1429 	  for (i=0; i!=MAX_CARDS; i++)
1430 	    {
1431 	      XtUnmanageChild(wCardToggle[i]);
1432 	      XtVaSetValues(wCardToggle[i], XtNstate, False, NULL);
1433 	    }
1434 
1435 	  for (i=0; i!=RISK_GetNumCardsOfPlayer(iCurrentPlayer); i++)
1436 	    CARDS_RenderCard(RISK_GetCardOfPlayer(iCurrentPlayer, i), i);
1437 	}
1438 
1439       /* Display the appropriate message for the state */
1440       UTIL_DisplayActionCString(iState, iCurrentPlayer);
1441     }
1442   else
1443 #ifdef ENGLISH
1444     UTIL_DisplayError("You must pick three cards of the same type or one of "
1445 		      "each type.");
1446 #endif
1447 #ifdef FRENCH
1448     UTIL_DisplayError("Vous devez choisir trois cartes du m�me type ou une de "
1449 		      "chaque type.");
1450 #endif
1451 }
1452 
1453 
1454 /************************************************************************
1455  *  FUNCTION: CBK_EndTurn
1456  *  HISTORY:
1457  *     03.05.94  ESF  Created.
1458  *     05.10.94  ESF  Fixed to do nothing unless game has started.
1459  *  PURPOSE:
1460  *  NOTES:
1461  ************************************************************************/
1462 void CBK_EndTurn(Widget w, XtPointer pData, XtPointer call_data)
1463 {
1464   char buf[256];
1465   UNUSED(w);
1466   UNUSED(pData);
1467   UNUSED(call_data);
1468 
1469   if (iState == STATE_REGISTER)
1470     return;
1471 
1472   if (fPlayingRemotely)
1473     {
1474 #ifdef ENGLISH
1475       snprintf(buf, sizeof(buf), "Come on, let %s finish playing...",
1476 	      RISK_GetNameOfPlayer(iCurrentPlayer));
1477 #endif
1478 #ifdef FRENCH
1479       snprintf(buf, sizeof(buf), "Come on, let %s finish playing...",
1480 	      RISK_GetNameOfPlayer(iCurrentPlayer));
1481 #endif
1482       UTIL_DisplayError(buf);
1483       return;
1484     }
1485 
1486   if (iState == STATE_PLACE || iState == STATE_FORTIFY)
1487 #ifdef ENGLISH
1488     UTIL_DisplayError("You must finish placing your armies.");
1489 #endif
1490 #ifdef FRENCH
1491     UTIL_DisplayError("Vous devez finir de placer vos arm�es.");
1492 #endif
1493   else
1494     {
1495       UTIL_DisplayError("");
1496       GAME_EndTurn();
1497     }
1498 }
1499 
1500 
1501 /************************************************************************
1502  *  FUNCTION: CBK_RefreshDice
1503  *  HISTORY:
1504  *     04.11.94  ESF  Created.
1505  *  PURPOSE:
1506  *  NOTES:
1507  ************************************************************************/
1508 void CBK_RefreshDice(Widget w, XtPointer pData, XtPointer call_data)
1509 {
1510   UNUSED(w);
1511   UNUSED(pData);
1512   UNUSED(call_data);
1513 
1514   DICE_Refresh();
1515 }
1516 
1517 
1518 /************************************************************************
1519  *  FUNCTION: CBK_About
1520  *  HISTORY:
1521  *     10.02.94  ESF  Created.
1522  *  PURPOSE:
1523  *  NOTES:
1524  ************************************************************************/
1525 void CBK_About(Widget w, XtPointer pData, XtPointer call_data)
1526 {
1527   char buf[256];
1528   UNUSED(w);
1529   UNUSED(pData);
1530   UNUSED(call_data);
1531 
1532   /* Print version information */
1533 #ifdef ENGLISH
1534   snprintf(buf, sizeof(buf), "This is Frisk version %s, compiled %s at %s",
1535 	  VERSION, __DATE__, __TIME__);
1536 #endif
1537 #ifdef FRENCH
1538   snprintf(buf, sizeof(buf),
1539 	   "Ceci est la version %s de Frisk, compil�e le %s � %s",
1540 	  VERSION, __DATE__, __TIME__);
1541 #endif
1542   UTIL_DisplayComment(buf);
1543 }
1544 
1545 
1546 /************************************************************************
1547  *  FUNCTION: CBK_Replicate
1548  *  HISTORY:
1549  *     02.26.95  ESF  Created.
1550  *  PURPOSE:
1551  *  NOTES:
1552  ************************************************************************/
1553 void CBK_Replicate(Int32 iMessType, void *pvMess, Int32 iType, Int32 iSrc)
1554 {
1555   UNUSED(iSrc);
1556 
1557   /*
1558    * Do the physical replication (send to the server so it will
1559    * broadcast it), but only if this callback is being called
1560    * for an outgoing message (i.e. a replication).  Otherwise,
1561    * a message just came in.
1562    */
1563 
1564   if (iType == MESS_OUTGOING)
1565     {
1566       (void)RISK_SendMessage(CLNT_GetCommLinkOfClient(CLNT_GetThisClientID()),
1567 			     iMessType, pvMess);
1568     }
1569 }
1570 
1571 
1572 /************************************************************************
1573  *  FUNCTION: CBK_Callback
1574  *  HISTORY:
1575  *     05.03.94  ESF  Created.
1576  *     08.18.94  ESF  Changed so that this does replication.
1577  *     01.09.95  ESF  Changed to not handle player death messages.
1578  *     01.17.95  ESF  Added to handle MsgDst changes entirely here.
1579  *     02.26.95  ESF  Modified to look more like a controller in MVC.
1580  *  PURPOSE:
1581  *  NOTES:
1582  ************************************************************************/
1583 void CBK_Callback(Int32 iMessType, void *pvMess)
1584 {
1585   /* Based on the message from the Model, perform some sort of action */
1586   switch (iMessType)
1587     {
1588     case MSG_OBJINTUPDATE:
1589       {
1590 	MsgObjIntUpdate *pMess = (MsgObjIntUpdate *)pvMess;
1591 	switch(pMess->iField)
1592 	  {
1593 	  case CNT_OWNER:
1594 	    {
1595 	      /* Recolor the country */
1596 	      if (pMess->iNewValue >= 0)
1597 		  COLOR_ColorCountry(pMess->iIndex1, pMess->iNewValue);
1598 	    }
1599 	    break;
1600 
1601 	  case CNT_NUMARMIES:
1602 	    {
1603 	      /* Redraw the number of armies on the country.  Since the
1604 	       * country may be lit up because of the notification, we
1605 	       * take pains not to mess with it.  Check to see if it was
1606 	       * lit up, and if so, leave it that way.
1607 	       */
1608 
1609 	      UTIL_PrintArmies(pMess->iIndex1, pMess->iNewValue,
1610 			       CLNT_GetLightCountOfCountry(pMess->iIndex1)>0 ?
1611 			       WhitePixel(hDisplay, 0) :
1612 			       BlackPixel(hDisplay, 0));
1613 	    }
1614 	    break;
1615 
1616   	  case PLR_ALLOCATION:
1617   	  {
1618   	    /* If a player was killed then remove it from the MsgDst list */
1619   	    if (pMess->iNewValue == ALLOC_NONE)
1620   	      {
1621   		Int32 iDeadPlayer = pMess->iIndex1;
1622   		Int32 i, iIndex;
1623   		Flag  fDone = FALSE;
1624 
1625   		/* Search for the entry to delete it */
1626   		for (i=1; i!=iIndexMD && !fDone; i++)
1627   		  if (piMsgDstPlayerID[i] == iDeadPlayer)
1628   		    fDone = TRUE;
1629 
1630   		D_Assert(fDone, "Something's wierd!");
1631 
1632   		/* Cause we want to recover somewhat if not debugging */
1633   		if (!fDone)
1634   		  break;
1635 
1636   		/* We found the entry.  Delete it and move the others down */
1637   		iIndex = (i-1);
1638 
1639   		MEM_Free(pstrMsgDstCString[iIndex]);
1640   		piMsgDstPlayerID[iIndex] = -1;
1641   		iIndexMD--;
1642 
1643   		for (i=iIndex; i<iIndexMD; i++)
1644   		  {
1645   		    pstrMsgDstCString[i] = pstrMsgDstCString[i+1];
1646   		    piMsgDstPlayerID[i] = piMsgDstPlayerID[i+1];
1647   		  }
1648 
1649   		UTIL_RefreshMsgDest(iIndexMD);
1650   	      }
1651 	    /* If the player was created, add it to the message box */
1652 	    else if (pMess->iNewValue == ALLOC_COMPLETE)
1653 	      {
1654 		/* A new player, add this to the list, update listbox */
1655 		char buf[256];
1656 		pstrMsgDstCString[iIndexMD] =
1657 		  (CString)MEM_Alloc(strlen(
1658 				      RISK_GetNameOfPlayer(pMess->iIndex1))+1);
1659 		piMsgDstPlayerID[iIndexMD] = pMess->iIndex1;
1660 		strcpy(pstrMsgDstCString[iIndexMD],
1661 		       RISK_GetNameOfPlayer(pMess->iIndex1));
1662 		UTIL_RefreshMsgDest(++iIndexMD);
1663 
1664 		/* Also create a message that tells of the new player */
1665 #ifdef ENGLISH
1666 		snprintf(buf, sizeof(buf), "%s (%s) has joined.",
1667 #endif
1668 #ifdef FRENCH
1669 		snprintf(buf, sizeof(buf), "%s (%s) est arriv�.",
1670 #endif
1671 			RISK_GetNameOfPlayer(pMess->iIndex1),
1672 			RISK_GetNameOfSpecies(
1673 				    RISK_GetSpeciesOfPlayer(pMess->iIndex1)));
1674 		UTIL_DisplayMessage(FROM_SERVER, DST_OTHER, buf);
1675 	      }
1676   	  }
1677   	  break;
1678 
1679 	  default:
1680 	    /* A message that we don't care about */
1681 	    ;
1682 	  }
1683 	break;
1684       }
1685 
1686     case MSG_OBJSTRUPDATE:
1687       {
1688 	MsgObjStrUpdate *pMess = (MsgObjStrUpdate *)pvMess;
1689 	switch (pMess->iField)
1690 	  {
1691 	  case PLR_COLORSTRING:
1692 	    {
1693 	      /* Allocate the color */
1694 	      COLOR_StoreNamedColor(pMess->strNewValue, pMess->iIndex1);
1695 	    }
1696 	    break;
1697 
1698 	  default:
1699 	    /* A message that we don't care about */
1700 	    ;
1701 	  }
1702       }
1703       break;
1704 
1705     default:
1706       /* Nothing... */
1707       ;
1708     }
1709 }
1710