1 /*
2 * file game_server.c - run game as server
3 *
4 * $Id: game_server.c,v 1.49 2006/02/18 21:40:02 fzago Exp $
5 *
6 * Program XBLAST
7 * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net)
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published
11 * by the Free Software Foundation; either version 2; or (at your option)
12 * any later version
13 *
14 * This program is distributed in the hope that it will be entertaining,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILTY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
17 * Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.
21 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */
23
24 #include "xblast.h"
25
26 #ifndef MAX_REJECTS
27 #define MAX_REJECTS 50
28 #endif
29
30 /*
31 * local variables
32 */
33 typedef struct
34 {
35 BMPlayer *ps; /* player stat pointer */
36 int cnt; /* player stat index */
37 } BotData;
38
39 static CFGGame serverGame;
40 static PlayerAction serverAction[MAX_PLAYER];
41 static XBBool playerLinked[MAX_PLAYER];
42 static int pa[MAX_PLAYER];
43 static int numActive;
44 static int teamActive;
45 static XBBool away;
46
47 /*
48 * mark all external hosts
49 */
50 static void
InitPlayerLink(void)51 InitPlayerLink (void)
52 {
53 int i;
54 assert (serverGame.players.num <= MAX_PLAYER);
55 for (i = 0; i < serverGame.players.num; i++) {
56 playerLinked[i] = (serverGame.players.host[i] != XBPH_Server &&
57 serverGame.players.host[i] != XBPH_Local &&
58 serverGame.players.host[i] != XBPH_Demo);
59 }
60 for (; i < MAX_PLAYER; i++) {
61 playerLinked[i] = XBFalse;
62 }
63 } /* InitPlayerLink */
64
65 /*
66 * set unlinked active external players to inactive
67 */
68 static XBBool
UpdatePlayerLink(void)69 UpdatePlayerLink (void)
70 {
71 int i;
72 XBBool result = XBFalse;
73 for (i = 0; i < serverGame.players.num; i++) {
74 if (serverGame.players.host[i] != XBPH_Server &&
75 serverGame.players.host[i] != XBPH_Local &&
76 !playerLinked[i] && !player_stat[i].in_active) {
77 player_stat[i].in_active = XBTrue;
78 player_stat[i].lives = 0;
79 result = XBTrue;
80 }
81 }
82 return result;
83 } /* UpdatePlayerLink */
84
85 /*
86 * determine number of potentially available players/teams
87 */
88 static void
GetActivePlayers(int * pa,int * pl,int * tm)89 GetActivePlayers (int *pa, int *pl, int *tm)
90 {
91 int i, reinco;
92 *pl = 0;
93 *tm = 0;
94 reinco = 0;
95 for (i = 0; i < serverGame.players.num; i++) {
96 pa[i] = !player_stat[i].in_active;
97 if (!player_stat[i].in_active) {
98 *pl = *pl + 1;
99 if (serverGame.setup.teamMode) {
100 if (!(reinco & (1 << player_stat[i].team))) {
101 reinco |= 1 << player_stat[i].team;
102 *tm = *tm + 1;;
103 }
104 }
105 else {
106 *tm = *tm + 1;;
107 }
108 }
109 }
110 Dbg_Game ("%i active player, %i active teams (%s mode)\n", *pl, *tm,
111 serverGame.setup.teamMode ? "team" : "chaos");
112 } /* GetActivePlayers */
113
114 /*
115 * update the central game with current result
116 */
117 static void
UpdateCentralGame(void)118 UpdateCentralGame (void)
119 {
120 int i;
121 static char res[20];
122 memset (res, 0, sizeof (res));
123 for (i = 0; i < serverGame.players.num; i++) {
124 sprintf (&res[i], "%i", player_stat[i].victories);
125 }
126 res[i] = '-';
127 Dbg_Game ("updating central game entry, current result %s\n", res);
128 Server_RestartNewGame (0, res);
129 } /* UpdateCentralGame */
130
131 /*
132 * check connections
133 */
134 static void
CheckConnections(void)135 CheckConnections (void)
136 {
137 int pl, id;
138 for (pl = 0; pl < serverGame.players.num; pl++) {
139 /* get host id of player */
140 id = serverGame.players.host[pl] + 1 - XBPH_Client1;
141 /* only check remote host players */
142 if (serverGame.players.host[pl] != XBPH_Server && serverGame.players.host[pl] != XBPH_Local) {
143 /* check if host disconnect has to be registered */
144 if (playerLinked[pl] && Network_GetHostState (id) == XBHS_None) {
145 /* unlink disconnected players */
146 playerLinked[pl] = XBFalse;
147 Dbg_Game ("player %u at host %u disconnected\n", pl, id);
148 }
149 /* override player action as suicide, if unlinked */
150 serverAction[pl].suicide = !playerLinked[pl];
151 }
152 }
153 } /* CheckConnections */
154
155 #ifdef DEBUG_GAME
156 /*
157 * output flag array (obsolete)
158 */
159 static char *
ShowPlayerFlags(XBBool * arr)160 ShowPlayerFlags (XBBool * arr)
161 {
162 static char tmp[MAX_PLAYER + 1];
163 int i;
164 memset (tmp, 0, sizeof (tmp));
165 for (i = 0; i < serverGame.players.num; i++) {
166 tmp[i] = arr[i] ? 'x' : '-';
167 }
168 return tmp;
169 } /* ShowPlayerFlags */
170 #endif
171
172 /*
173 * server waits until all clients sent given event
174 */
175 static XBBool
WaitForClientEvent(XBNetworkEvent waitEvent,XBBool needFlush)176 WaitForClientEvent (XBNetworkEvent waitEvent, XBBool needFlush)
177 {
178 int i;
179 long num;
180 unsigned id;
181 XBEventCode xbEvent;
182 XBEventData eData;
183 XBNetworkEvent netEvent;
184 XBBool playerWait[MAX_PLAYER];
185
186 /* determine for which players we have to wait */
187 memcpy (playerWait, playerLinked, sizeof (playerWait));
188 Dbg_Game ("linked players |%s|\n", ShowPlayerFlags (playerLinked));
189 /* set timer, disable keys/mouse */
190 GUI_SetTimer (FRAME_TIME, XBTrue);
191 GUI_SetKeyboardMode (KB_XBLAST);
192 GUI_SetMouseMode (XBFalse);
193 /* loop until all clients have reported back */
194 do {
195 /* determine how many player are not ready */
196 num = 0;
197 for (i = 0; i < serverGame.players.num; i++) {
198 if (playerWait[i]) {
199 num++;
200 }
201 }
202 Dbg_Game ("waiting for players |%s|, remaining = %lu\n", ShowPlayerFlags (playerWait),
203 (unsigned long)num);
204 /* update window */
205 GameUpdateWindow ();
206 /* get next event */
207 xbEvent = GUI_WaitEvent (&eData);
208 /* check for network events when timer triggers */
209 switch (xbEvent) {
210 case XBE_XBLAST:
211 /* check for escape */
212 if (eData.value == XBXK_EXIT) {
213 return XBFalse;
214 }
215 break;
216 case XBE_TIMER:
217 /* try to flush udp connections, if requested */
218 if (needFlush) {
219 needFlush = Server_FlushPlayerAction ();
220 }
221 /* get single network event */
222 netEvent = Network_GetEvent (&id);
223 /* check for sync, error or disconnect */
224 if (netEvent == waitEvent || netEvent == XBNW_Error || netEvent == XBNW_Disconnected) {
225 if (id < MAX_HOSTS) {
226 /* calculate host type */
227 XBPlayerHost host = XBPH_Client1 + id - 1;
228 /* loop through all players on that host */
229 for (i = 0; i < serverGame.players.num; i++) {
230 if (serverGame.players.host[i] == host) {
231 /* mark as having responded */
232 playerWait[i] = XBFalse;
233 /* unlink if disconnected */
234 switch (netEvent) {
235 case XBNW_Error:
236 Dbg_Game ("unlinked player %i (host %i), network error\n", i, id);
237 playerLinked[i] = XBFalse;
238 break;
239 case XBNW_Disconnected:
240 Dbg_Game ("unlinked player %i, (host %i), disconnected\n", i, id);
241 playerLinked[i] = XBFalse;
242 break;
243 default:
244 break;
245 }
246 }
247 }
248 }
249 else {
250 Dbg_Game ("network event %i on central connection \n", id);
251 }
252 }
253 break;
254 default:
255 /* check for chat event */
256 (void)Chat_Event (xbEvent, eData);
257 break;
258 }
259 /* TODO: limit timer events and disconnect all non-responding hosts */
260 } while (num > 0);
261 return XBTrue;
262 } /* WaitForClientEvent */
263
264 /*
265 * server waits for specific event from all clients and acknowledges
266 */
267 static XBBool
SyncWithClients(XBNetworkEvent syncEvent,XBBool needFlush,XBBool showMsg)268 SyncWithClients (XBNetworkEvent syncEvent, XBBool needFlush, XBBool showMsg)
269 {
270 if (showMsg) {
271 SetMessage ("Waiting for others ...", XBTrue);
272 }
273 WaitForClientEvent (syncEvent, needFlush);
274 /* acknowledge receiving all syncs and go on */
275 Server_SendSync (syncEvent);
276 return XBTrue;
277 } /* SyncWithClients */
278
279 /*
280 * insert keys from clients
281 */
282 static void
InsertClientAction(const CFGGamePlayers * cfgPlayers,PlayerAction * serverAction)283 InsertClientAction (const CFGGamePlayers * cfgPlayers, PlayerAction * serverAction)
284 {
285 int i;
286 assert (NULL != cfgPlayers);
287 assert (NULL != serverAction);
288 for (i = 0; i < cfgPlayers->num; i++) {
289 switch (cfgPlayers->host[i]) {
290 case XBPH_Client1:
291 Server_GetPlayerAction (1, i, serverAction + i);
292 break;
293 case XBPH_Client2:
294 Server_GetPlayerAction (2, i, serverAction + i);
295 break;
296 case XBPH_Client3:
297 Server_GetPlayerAction (3, i, serverAction + i);
298 break;
299 case XBPH_Client4:
300 Server_GetPlayerAction (4, i, serverAction + i);
301 break;
302 case XBPH_Client5:
303 Server_GetPlayerAction (5, i, serverAction + i);
304 break;
305 #ifdef SMPF
306 case XBPH_Client6:
307 Server_GetPlayerAction (6, i, serverAction + i);
308 break;
309 case XBPH_Client7:
310 Server_GetPlayerAction (7, i, serverAction + i);
311 break;
312 case XBPH_Client8:
313 Server_GetPlayerAction (8, i, serverAction + i);
314 break;
315 case XBPH_Client9:
316 Server_GetPlayerAction (9, i, serverAction + i);
317 break;
318 case XBPH_Client10:
319 Server_GetPlayerAction (10, i, serverAction + i);
320 break;
321 case XBPH_Client11:
322 Server_GetPlayerAction (11, i, serverAction + i);
323 break;
324 case XBPH_Client12:
325 Server_GetPlayerAction (12, i, serverAction + i);
326 break;
327 case XBPH_Client13:
328 Server_GetPlayerAction (13, i, serverAction + i);
329 break;
330 case XBPH_Client14:
331 Server_GetPlayerAction (14, i, serverAction + i);
332 break;
333 case XBPH_Client15:
334 Server_GetPlayerAction (15, i, serverAction + i);
335 break;
336 #endif
337 default:
338 break;
339 }
340 }
341 Server_ClearPlayerAction ();
342 } /* InsertClientAction */
343
344 /*
345 * run a level
346 */
347 static int
ServerRunLevel(int numActive,const DBRoot * level)348 ServerRunLevel (int numActive, const DBRoot * level)
349 {
350 int gameTime;
351 int pauseStatus;
352 int lastTeam, counter, winner;
353 int remainingTeams;
354 int frameTime;
355 BMPlayer *ps;
356 XBBool async;
357 const char *msg;
358 XBEventData eData;
359
360 /* sanity check */
361 assert (level != NULL);
362 /* necessary inits */
363 winner = -1;
364 gameTime = 0;
365 pauseStatus = -1;
366 lastTeam = -1;
367 frameTime = serverGame.setup.frameRate ? 1000 / serverGame.setup.frameRate : 0;
368 /* start demo recording if requested */
369 if (serverGame.setup.recordDemo) {
370 DemoInitLevel (DB_Atom (level));
371 }
372 /* post level name on chat */
373 Server_SysChat (TempString ("playing level [%s]", GetLevelName (level)));
374 /* Config level */
375 if (!ConfigLevel (level)) {
376 Dbg_Game ("level config failed!\n");
377 goto Exit;
378 }
379 /* prepare async check at end of level */
380 Server_ClearLevelWinners ();
381 /* clean up player actions */
382 Server_ClearPlayerAction ();
383 Server_ResetPlayerAction ();
384 /* level intro */
385 if (!LevelIntro (serverGame.players.num, level, away ? -1 : serverGame.setup.infoTime)) {
386 Dbg_Game ("abort in level intro\n");
387 goto Exit;
388 }
389 /* wait for clients to show level info */
390 Dbg_Game ("waiting for clients to show level intro\n");
391 SyncWithClients (XBNW_SyncLevelIntro, XBFalse, XBTrue);
392 Dbg_Game ("clients show level intro\n");
393 /* determine active players/teams */
394 UpdatePlayerLink ();
395 GetActivePlayers (pa, &numActive, &teamActive);
396 if (teamActive <= 1) {
397 GUI_ErrorMessage ("Only one team left after level-intro sync!");
398 goto Exit;
399 }
400 remainingTeams = teamActive;
401 /* show level map */
402 LevelBegin (GetLevelName (level));
403 /* set timer for frames */
404 GUI_SetTimer (frameTime, XBTrue);
405 /* process key events */
406 GUI_SetKeyboardMode (KB_XBLAST);
407 GUI_SetMouseMode (XBFalse);
408 /* play music, if requested */
409 if (serverGame.setup.Music) {
410 SND_Load (serverGame.setup.Music);
411 SND_Play (serverGame.setup.Music, SOUND_MIDDLE_POSITION);
412 }
413 Dbg_Game ("starting level!\n");
414 /* update central entry */
415 UpdateCentralGame ();
416 /* now start level */
417 do {
418 /* ready input */
419 ClearPlayerAction (serverAction);
420 /* handle all event until timer triggers */
421 if (!GameEventLoop (XBE_TIMER, &eData)) {
422 Dbg_Game ("game aborted during level\n");
423 goto Exit;
424 }
425 /* increment game clock */
426 gameTime++;
427 /* update game entry occasionally */
428 if (serverGame.host.central && (gameTime % 1024) == 0) {
429 UpdateCentralGame ();
430 }
431 /* server bot */
432 Player_BotAction (serverAction);
433 /* handle game turn */
434 GameTurn (gameTime, serverGame.players.num, &remainingTeams);
435 /* insert any data received from clients */
436 InsertClientAction (&serverGame.players, serverAction);
437 /* trigger suicides for disconnected clients */
438 CheckConnections ();
439 /* send all data on player actions to clients */
440 Server_SendPlayerAction (gameTime, serverAction);
441 /* record demo data if requested */
442 if (serverGame.setup.recordDemo) {
443 DemoRecordFrame (gameTime, serverAction);
444 }
445 /* evaluate player action */
446 (void)GameEvalAction (serverGame.players.num, serverAction);
447 /* update window */
448 GameUpdateWindow ();
449 } while (gameTime < GAME_TIME &&
450 remainingTeams > 0 && (remainingTeams > 1 || NumberOfExplosions () != 0));
451 /* tell client game is over */
452 Server_FinishPlayerAction (gameTime + 1);
453 /* check/reset away flags, make away bots */
454 Player_CheckLocalAway ();
455 /* now update away flag for local players */
456 away = Player_CheckLocalBot ();
457 /* calc last team for async check, do not store yet */
458 LevelResult (gameTime, &lastTeam, serverGame.players.num, level, XBFalse);
459 /* count number of players in winner team */
460 if (lastTeam <= MAX_PLAYER) {
461 for (ps = player_stat, counter = 1; ps < player_stat + serverGame.players.num;
462 ps++, counter++) {
463 if (ps->team == lastTeam) {
464 winner = counter;
465 }
466 }
467 }
468 /* finish demo file if requested */
469 if (serverGame.setup.recordDemo) {
470 DemoFinishLevel (gameTime, winner, "s");
471 }
472 Dbg_Game ("waiting for clients to send winner\n");
473 Server_ReceiveWinnerTeam (0, lastTeam);
474 WaitForClientEvent (XBNW_SyncLevelResult, XBTrue);
475 /* determine active players/teams */
476 UpdatePlayerLink ();
477 GetActivePlayers (pa, &numActive, &teamActive);
478 if (teamActive <= 1) {
479 GUI_ErrorMessage ("Only one team left after async check!");
480 goto Exit;
481 }
482 async = Server_LevelAsync ();
483 if (async) {
484 Dbg_Game ("async result determined, informing clients!\n");
485 Server_SendLevelAsync ();
486 GUI_ErrorMessage ("Async level, making it a draw\n");
487 lastTeam = MAX_PLAYER;
488 }
489 else {
490 Dbg_Game ("results sync, informing clients!\n");
491 Server_SendLevelSync ();
492 /* now store the level result */
493 msg = LevelResult (gameTime, &lastTeam, serverGame.players.num, level, XBTrue);
494 if (!LevelEnd (serverGame.players.num, lastTeam, msg, away ? -1 : 1)) {
495 lastTeam = -1;
496 }
497 }
498 Exit:
499 /* stop music if necessary */
500 if (serverGame.setup.Music) {
501 SND_Stop (serverGame.setup.Music);
502 }
503 FinishLevel ();
504 DeleteAllExplosions ();
505 /* fade out image */
506 DoFade (XBFM_BLACK_OUT, PIXH + 1);
507 /* that's all */
508 return lastTeam;
509 } /* ServerRunLevel */
510
511 /*
512 * send level data to clients
513 */
514 static XBBool
SendLevelToClients(const DBRoot ** level)515 SendLevelToClients (const DBRoot ** level)
516 {
517 int okay = MAX_REJECTS;
518 /* send level data to clients */
519 while (okay > 0) {
520 *level = LoadLevelFile (GetNextLevel ());
521 Dbg_Game ("Proposed level is: %s\n", GetLevelName (*level));
522 Server_SendLevel (*level);
523 Server_ClearLevelStatus ();
524 Server_SetLevelStatus (0, XBTrue);
525 WaitForClientEvent (XBNW_LevelConfig, XBTrue);
526 if (Server_LevelApproved ()) {
527 Dbg_Game ("Level accepted by all clients\n");
528 /* now set and send fresh random seed so that it arrives before the activate */
529 SeedRandom (time (NULL));
530 Server_SendRandomSeed ();
531 Server_SendLevelActivate ();
532 Dbg_Game ("negotiations finished, proceeding\n");
533 return (0);
534 }
535 else {
536 okay--;
537 Dbg_Game ("Level rejected (%i attempt(s) remaining)\n", okay);
538 Server_SendLevelReset ();
539 }
540 }
541 Dbg_Game ("negotiations failed!\n");
542 return (-1);
543 } /* SendLevelToClients */
544
545 /*
546 * run the game as server
547 */
548 void
RunServerGame(void)549 RunServerGame (void)
550 {
551 const DBRoot *level;
552 int lastTeam, winner, maxNumWins;
553 int i;
554 CFGCentralSetup central;
555
556 /* get setup */
557 if (!RetrieveGame (CT_Remote, SERVERGAMECONFIG, &serverGame)) {
558 Dbg_Game ("failed to get game setup!\n");
559 goto Disconnect;
560 }
561 /* select levels to play */
562 if (!InitLevels (&serverGame)) {
563 Dbg_Game ("failed to initialize levels!\n");
564 goto Disconnect;
565 }
566 /* common inits */
567 if (!InitGame (XBPH_Server, CT_Remote, &serverGame, serverAction)) {
568 Dbg_Game ("failed to initialize game!\n");
569 goto Disconnect;
570 }
571 /* local data */
572 maxNumWins = 0;
573 winner = -1;
574 numActive = 0;
575 teamActive = 0;
576 away = Player_CheckLocalBot ();
577 Dbg_Game ("server game initialized\n");
578 /* mark external hosts */
579 InitPlayerLink ();
580 /* wait for clients to initialize game */
581 Dbg_Game ("waiting for clients to init game\n");
582 SyncWithClients (XBNW_SyncEndOfInit, XBFalse, XBFalse);
583 Dbg_Game ("clients have initialized game\n");
584 /* determine active players/teams */
585 UpdatePlayerLink ();
586 GetActivePlayers (pa, &numActive, &teamActive);
587 if (teamActive <= 1) {
588 GUI_ErrorMessage ("Only one team left after game init!");
589 goto Disconnect;
590 }
591 /* Connect to central */
592 if (serverGame.setup.rated) {
593 SetMessage ("Connecting to central...", XBTrue);
594 Dbg_Game ("rated game requested\n");
595 RetrieveCentralSetup (¢ral);
596 if (User_Connect (¢ral)) {
597 Dbg_Game ("Connection to central established\n");
598 }
599 else {
600 Dbg_Game ("failed to establish connection to central, unrated game\n");
601 }
602 }
603 else {
604 Dbg_Game ("unrated game requested\n");
605 }
606 /* play levels */
607 do {
608 /* negotiate next level */
609 if (SendLevelToClients (&level) == -1) {
610 GUI_ErrorMessage ("Level negotiations failed!");
611 goto Exit;
612 }
613 /* update active players/teams */
614 UpdatePlayerLink ();
615 GetActivePlayers (pa, &numActive, &teamActive);
616 if (teamActive <= 1) {
617 GUI_ErrorMessage ("Only one team left after level negotiation!");
618 goto Exit;
619 }
620 /* play level */
621 lastTeam = ServerRunLevel (teamActive, level);
622 /* check for quick exit */
623 if (-1 == lastTeam) {
624 Dbg_Game ("server aborted game\n");
625 goto Exit;
626 }
627 /* update current winner */
628 Dbg_Game ("team #%i won the level\n", lastTeam);
629 for (i = 0; i < serverGame.players.num; i++) {
630 if (player_stat[i].victories > maxNumWins) {
631 maxNumWins = player_stat[i].victories;
632 winner = i;
633 }
634 }
635 /* wait for clients to reach level end */
636 Dbg_Game ("waiting for clients to show level end\n");
637 SyncWithClients (XBNW_SyncLevelEnd, XBFalse, XBTrue);
638 Dbg_Game ("waiting for clients to show level end\n");
639 /* update active players/teams */
640 UpdatePlayerLink ();
641 GetActivePlayers (pa, &numActive, &teamActive);
642 if (teamActive <= 1) {
643 GUI_ErrorMessage ("Only one team left after level-end sync!");
644 goto Exit;
645 }
646 /* send level stats to central if rated game */
647 if (User_Connected ()) {
648 Dbg_Game ("sending level results to central\n");
649 User_SendGameStat (serverGame.players.num, player_stat, pa);
650 }
651 else {
652 Dbg_Game ("connection to central has broken down, no more ratings will be sent\n");
653 }
654 /* show scores */
655 if (!ShowScoreBoard
656 (lastTeam, maxNumWins, serverGame.players.num, player_stat, away ? -1 : 1)) {
657 Dbg_Game ("game exit during Scoreboard\n");
658 goto Exit;
659 }
660 /* wait for clients to show scoreboard */
661 Dbg_Game ("waiting for clients to show score\n");
662 SyncWithClients (XBNW_SyncScoreboard, XBFalse, XBTrue);
663 Dbg_Game ("clients show score\n");
664 /* determine number of active players */
665 UpdatePlayerLink ();
666 GetActivePlayers (pa, &numActive, &teamActive);
667 } while (numActive > 1 && teamActive > 1 && maxNumWins < serverGame.setup.numWins);
668 /* and the winner is ... */
669 if (maxNumWins >= serverGame.setup.numWins) {
670 Dbg_Game ("team #%i won the game!\n", winner);
671 if (User_Connected ()) {
672 Dbg_Game ("Sending final game result to central\n");
673 for (i = 0; i < serverGame.players.num; i++) {
674 pa[i] = 1;
675 if (player_stat[i].victories == serverGame.setup.numWins) {
676 player_stat[i].lives = -player_stat[i].victories;
677 }
678 else {
679 player_stat[i].lives = player_stat[i].victories;
680 }
681 }
682 User_SendGameStat (-serverGame.players.num, player_stat, pa);
683 }
684 /* determine and show winner */
685 InitWinner (serverGame.players.num);
686 ShowWinner (lastTeam, serverGame.players.num, player_stat);
687 }
688 else {
689 Dbg_Game ("game finished, too few players - current winner %i\n", winner);
690 GUI_ErrorMessage ("Not enough players/teams left in the game");
691 }
692 Exit:
693 FinishGame (&serverGame);
694 Disconnect:
695 Dbg_Game ("disconnecting all clients\n");
696 Server_SendDisconnectAll ();
697 if (User_Connected ()) {
698 Dbg_Game ("disconnecting result link to central\n");
699 User_SendDisconnect ();
700 User_Disconnect ();
701 }
702 Dbg_Game ("closing game entry in central\n");
703 Server_CloseNewGame ();
704 return;
705 } /* StartServerGame */
706
707 /*
708 * end of file game_server.c
709 */
710