tserverside.c - vaccinewars - be a doctor and try to vaccinate the world
git clone git://src.adamsgaard.dk/vaccinewars
Log
Files
Refs
README
LICENSE
---
tserverside.c (111645B)
---
     1 /************************************************************************
     2  * serverside.c   Handles the server side of dopewars                   *
     3  * Copyright (C)  1998-2021  Ben Webb                                   *
     4  *                Email: benwebb@users.sf.net                           *
     5  *                WWW: https://dopewars.sourceforge.io/                 *
     6  *                                                                      *
     7  * This program is free software; you can redistribute it and/or        *
     8  * modify it under the terms of the GNU General Public License          *
     9  * as published by the Free Software Foundation; either version 2       *
    10  * of the License, or (at your option) any later version.               *
    11  *                                                                      *
    12  * This program is distributed in the hope that it will be useful,      *
    13  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *
    14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
    15  * GNU General Public License for more details.                         *
    16  *                                                                      *
    17  * You should have received a copy of the GNU General Public License    *
    18  * along with this program; if not, write to the Free Software          *
    19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston,               *
    20  *                   MA  02111-1307, USA.                               *
    21  ************************************************************************/
    22 
    23 #ifdef HAVE_CONFIG_H
    24 #include 
    25 #endif
    26 
    27 #include 
    28 #include 
    29 #include           /* For size_t etc. */
    30 #include 
    31 
    32 #ifdef CYGWIN
    33 #include            /* For network functions */
    34 #include             /* For datatypes such as BOOL */
    35 #include             /* For getpid */
    36 #else
    37 #include          /* For struct sockaddr etc. */
    38 #include          /* For struct sockaddr_in etc. */
    39 #include              /* For struct sockaddr_un */
    40 #include           /* For socklen_t */
    41 #endif /* CYGWIN */
    42 
    43 #ifdef HAVE_UNISTD_H
    44 #include 
    45 #endif
    46 #include 
    47 #include 
    48 #include 
    49 #include 
    50 #include "configfile.h"         /* For UpdateConfigFile */
    51 #include "dopewars.h"
    52 #include "log.h"
    53 #include "message.h"
    54 #include "network.h"
    55 #include "nls.h"
    56 #include "serverside.h"
    57 #include "tstring.h"
    58 #include "util.h"
    59 
    60 #ifdef GUI_SERVER
    61 #include "gtkport/gtkport.h"
    62 #endif
    63 
    64 static const price_t MINTRENCHPRICE = 200, MAXTRENCHPRICE = 300;
    65 
    66 #define ESCAPE      0
    67 #define DEFECT      1
    68 #define SHOT        2
    69 #define NUMDISCOVER 3
    70 char *Discover[NUMDISCOVER] = {
    71   /* Things that can "happen" to your spies - look for strings containing
    72      "The spy %s!" to see how these strings are used. */
    73   N_("escaped"), N_("defected"), N_("was shot")
    74 };
    75 
    76 /* The two keys that are valid answers to the Attack/Evade question. If
    77    you wish to translate them, do so in the same order as they given here.
    78    You will also need to translate the answers given by the clients. */
    79 static char *attackquestiontr = N_("AE");
    80 
    81 /* If we haven't talked to the metaserver for 3 hours, then remind it that
    82  * we still exist, so we don't get wiped from the list of active servers */
    83 #define METAUPDATETIME  (10800)
    84 
    85 /* Don't report players logging in/out to the metaserver more frequently
    86  * than once every minute (so as not to overload the metaserver, or slow
    87  * down our own server). */
    88 #define METAMINTIME (60)
    89 
    90 int TerminateRequest, ReregisterRequest, RelogRequest;
    91 
    92 int MetaUpdateTimeout;
    93 long MetaMinTimeout;
    94 gboolean WantQuit = FALSE;
    95 
    96 #ifdef CYGWIN
    97 static SERVICE_STATUS_HANDLE scHandle;
    98 #endif
    99 
   100 #ifdef GUI_SERVER
   101 static gboolean glib_timeout(gpointer userp);
   102 static gboolean glib_socket(GIOChannel *ch, GIOCondition condition,
   103                             gpointer data);
   104 #endif
   105 
   106 
   107 /* Do we want to update the player details on the metaserver when the
   108  * timeout expires? */
   109 gboolean MetaPlayerPending = FALSE;
   110 
   111 GSList *FirstServer = NULL;
   112 
   113 #ifdef NETWORKING
   114 /* Data waiting to be sent to/read from the metaserver */
   115 static CurlConnection MetaConn;
   116 
   117 static GScanner *Scanner;
   118 
   119 #endif
   120 
   121 /* Handle to the high score file */
   122 static FILE *ScoreFP = NULL;
   123 
   124 /* Pointer to the filename of a pid file (if non-NULL) */
   125 char *PidFile = NULL;
   126 
   127 static char HelpText[] = {
   128   /* Help on various general server commands */
   129   N_("dopewars server version %s commands and settings\n\n"
   130      "help                     Displays this help screen\n"
   131      "list                     Lists all players logged on\n"
   132      "push             Politely asks the named player to leave\n"
   133      "kill             Abruptly breaks the connection with the "
   134      "named player\n"
   135      "msg:               Send message to all players\n"
   136      "save               Save current configuration to the named file\n"
   137      "quit                     Gracefully quit, after notifying all players\n"
   138      "=       Sets the named variable to the given value\n"
   139      "               Displays the value of the named variable\n"
   140      "[x].=  Sets the named variable in the given list,\n"
   141      "                         index x, to the given value\n"
   142      "[x].          Displays the value of the named list variable\n"
   143      "\nValid variables are listed below:-\n\n")
   144 };
   145 
   146 typedef enum _OfferForce {
   147   NOFORCE, FORCECOPS, FORCEBITCH
   148 } OfferForce;
   149 
   150 int SendSingleHighScore(Player *Play, struct HISCORE *Score,
   151                         int ind, gboolean Bold);
   152 static int SendCopOffer(Player *To, OfferForce Force);
   153 static int OfferObject(Player *To, gboolean ForceBitch);
   154 static gboolean HighScoreWrite(FILE *fp, struct HISCORE *MultiScore,
   155                                struct HISCORE *AntiqueScore);
   156 
   157 #ifdef NETWORKING
   158 static void MetaConnectError(CurlConnection *conn, GError *err)
   159 {
   160   dopelog(1, LF_SERVER, _("Failed to connect to metaserver at %s (%s)"),
   161           MetaServer.URL, err->message);
   162 }
   163 
   164 void log_meta_headers(gpointer data, gpointer user_data)
   165 {
   166   char *header = data;
   167   if (*header)
   168     dopelog(4, LF_SERVER, _("MetaServer: %s"), header);
   169 }
   170 #endif
   171 
   172 /* 
   173  * Sends server details to the metaserver, if specified. If "Up" is
   174  * TRUE, informs the metaserver that the server is now accepting
   175  * connections - otherwise tells the metaserver that this server is
   176  * about to go down. If "SendData" is TRUE, then also sends game
   177  * data (e.g. scores) to the metaserver. If "RespectTimeout" is TRUE
   178  * then the update is delayed if a previous update happened too
   179  * recently. If networking is disabled, this function does nothing.
   180  */
   181 void RegisterWithMetaServer(gboolean Up, gboolean SendData,
   182                             gboolean RespectTimeout)
   183 {
   184 #ifdef NETWORKING
   185   struct HISCORE MultiScore[NUMHISCORE], AntiqueScore[NUMHISCORE];
   186   GString *body;
   187   gchar *prstr;
   188   gboolean ret;
   189   GError *tmp_error = NULL;
   190   int i;
   191 
   192   if (!MetaServer.Active || WantQuit || !Server) {
   193     return;
   194   }
   195 
   196   if (MetaMinTimeout > time(NULL) && RespectTimeout) {
   197     dopelog(3, LF_SERVER,
   198             _("Attempt to connect to metaserver too frequently "
   199               "- waiting for next timeout"));
   200     MetaPlayerPending = TRUE;
   201     return;
   202   }
   203 
   204   body = g_string_new("");
   205 
   206   g_string_assign(body, "output=text&");
   207 
   208   g_string_append_printf(body, "up=%d&port=%d&version=", Up ? 1 : 0, Port);
   209   AddURLEnc(body, VERSION);
   210   g_string_append_printf(body, "&players=%d&maxplay=%d&comment=",
   211                     CountPlayers(FirstServer), MaxClients);
   212   AddURLEnc(body, MetaServer.Comment);
   213 
   214   if (MetaServer.LocalName[0]) {
   215     g_string_append(body, "&hostname=");
   216     AddURLEnc(body, MetaServer.LocalName);
   217   }
   218   if (MetaServer.Password[0]) {
   219     g_string_append(body, "&password=");
   220     AddURLEnc(body, MetaServer.Password);
   221   }
   222 
   223   if (SendData && HighScoreRead(ScoreFP, MultiScore, AntiqueScore, TRUE)) {
   224     for (i = 0; i < NUMHISCORE; i++) {
   225       if (MultiScore[i].Name && MultiScore[i].Name[0]) {
   226         g_string_append_printf(body, "&nm[%d]=", i);
   227         AddURLEnc(body, MultiScore[i].Name);
   228         g_string_append_printf(body, "&dt[%d]=", i);
   229         AddURLEnc(body, MultiScore[i].Time);
   230         g_string_append_printf(body, "&st[%d]=%s&sc[%d]=", i,
   231                           MultiScore[i].Dead ? "dead" : "alive", i);
   232         AddURLEnc(body, prstr = FormatPrice(MultiScore[i].Money));
   233         g_free(prstr);
   234       }
   235     }
   236   }
   237 
   238   ret = OpenCurlConnection(&MetaConn, MetaServer.URL, body->str, &tmp_error);
   239 
   240   dopelog(2, LF_SERVER, _("Waiting for connect to metaserver at %s..."),
   241           MetaServer.URL);
   242   g_string_free(body, TRUE);
   243   if (!ret) {
   244     MetaConnectError(&MetaConn, tmp_error);
   245     g_error_free(tmp_error);
   246   }
   247   MetaPlayerPending = FALSE;
   248 
   249   MetaUpdateTimeout = time(NULL) + METAUPDATETIME;
   250   MetaMinTimeout = time(NULL) + METAMINTIME;
   251 #endif /* NETWORKING */
   252 }
   253 
   254 #ifdef NETWORKING
   255 void HandleServerPlayer(Player *Play)
   256 {
   257   gchar *buf;
   258   gboolean MessageRead = FALSE;
   259 
   260   while ((buf = GetWaitingPlayerMessage(Play)) != NULL) {
   261     MessageRead = TRUE;
   262     HandleServerMessage(buf, Play);
   263     g_free(buf);
   264   }
   265   /* Reset the idle timeout (if necessary) */
   266   if (MessageRead && IdleTimeout) {
   267     Play->IdleTimeout = time(NULL) + (time_t) IdleTimeout;
   268   }
   269 }
   270 #endif /* NETWORKING */
   271 
   272 /* 
   273  * Sends details (name, ID) about player "Play" to player "To", using
   274  * message code "Code".
   275  */
   276 void SendPlayerDetails(Player *Play, Player *To, MsgCode Code)
   277 {
   278   GString *text;
   279 
   280   text = g_string_new(GetPlayerName(Play));
   281   if (HaveAbility(To, A_PLAYERID)) {
   282     g_string_append_printf(text, "^%d", Play->ID);
   283   }
   284   SendServerMessage(NULL, C_NONE, Code, To, text->str);
   285   g_string_free(text, TRUE);
   286 }
   287 
   288 /*
   289  * Checks the version of the client that has connected, and sends a
   290  * warning message if it's old.
   291  */
   292 void RemoteVersionCheck(Player *Play)
   293 {
   294   /* Client didn't send a C_ABILITIES message at all, so is either broken
   295    * or is version 1.4.8 or earlier. */
   296   if (Play->Abil.RemoteNum == 0) {
   297     SendPrintMessage(NULL, C_VERSIONCHECK, Play,
   298         _("You appear to be using an extremely old (version 1.4.x) client.^"
   299           "While this will probably work, many of the newer features^"
   300           "will be unsupported. Get the latest version from the^"
   301           "dopewars website, https://dopewars.sourceforge.io/."));
   302 
   303   /* The client has a smaller value of A_NUM; this means that not only does
   304    * it not support some features, it doesn't even know they might exist. */
   305   } else if (Play->Abil.RemoteNum < A_NUM) {
   306     SendPrintMessage(NULL, C_VERSIONCHECK, Play,
   307         _("Warning: your client is too old to support all of this^"
   308           "server's features. For the full \"experience\", get^"
   309           "the latest version of dopewars from the^"
   310           "website, https://dopewars.sourceforge.io/."));
   311   }
   312 
   313   /* Otherwise, the client is either the same version as the server, or
   314    * it's newer. Both should be OK, so do nothing. */
   315 }
   316 
   317 /* 
   318  * Given a message "buf", from player "Play", performs processing and
   319  * sends suitable replies.
   320  */
   321 void HandleServerMessage(gchar *buf, Player *Play)
   322 {
   323   Player *To, *tmp, *pt;
   324   GSList *list;
   325   char *Data;
   326   AICode AI;
   327   MsgCode Code;
   328   gchar *text;
   329   DopeEntry NewEntry;
   330   int i;
   331   price_t money;
   332 
   333   if (ProcessMessage(buf, Play, &To, &AI, &Code, &Data, FirstServer) == -1) {
   334     g_warning("Bad message");
   335     return;
   336   }
   337   switch (Code) {
   338   case C_MSGTO:
   339     if (Network) {
   340       dopelog(3, LF_SERVER, "%s->%s: %s", GetPlayerName(Play),
   341               GetPlayerName(To), Data);
   342     }
   343     SendServerMessage(Play, AI, Code, To, Data);
   344     break;
   345   case C_ABILITIES:
   346     ReceiveAbilities(Play, Data);
   347     break;
   348   case C_NAME:
   349     StripTerminators(Data);
   350     pt = GetPlayerByName(Data, FirstServer);
   351     if (pt && pt != Play) {
   352       if (ConnectTimeout) {
   353         Play->ConnectTimeout = time(NULL) + (time_t) ConnectTimeout;
   354       }
   355       SendServerMessage(NULL, C_NONE, C_NEWNAME, Play, NULL);
   356     } else if (strlen(GetPlayerName(Play)) == 0 && Data[0]) {
   357       if (CountPlayers(FirstServer) < MaxClients || !Network) {
   358         RemoteVersionCheck(Play);
   359         SendAbilities(Play);
   360         CombineAbilities(Play);
   361         SendInitialData(Play);
   362         SendMiscData(Play);
   363         SetPlayerName(Play, Data);
   364         for (list = FirstServer; list; list = g_slist_next(list)) {
   365           pt = (Player *)list->data;
   366           if (pt != Play && IsConnectedPlayer(pt) && !IsCop(pt)) {
   367             SendPlayerDetails(pt, Play, C_LIST);
   368           }
   369         }
   370         if (ServerMOTD && ServerMOTD[0]) {
   371           SendPrintMessage(NULL, C_MOTD, Play, ServerMOTD);
   372         }
   373         SendServerMessage(NULL, C_NONE, C_ENDLIST, Play, NULL);
   374         RegisterWithMetaServer(TRUE, FALSE, TRUE);
   375         Play->ConnectTimeout = 0;
   376 
   377         if (Network) {
   378           dopelog(2, LF_SERVER, _("%s joins the game!"), GetPlayerName(Play));
   379         }
   380         for (list = FirstServer; list; list = g_slist_next(list)) {
   381           pt = (Player *)list->data;
   382           if (IsConnectedPlayer(pt) && pt != Play) {
   383             SendPlayerDetails(Play, pt, C_JOIN);
   384           }
   385         }
   386         Play->EventNum = E_ARRIVE;
   387         SendPlayerData(Play);
   388         SendEvent(Play);
   389       } else {
   390         /* Message displayed in the server when too many players try to
   391          * connect */
   392         dopelog(2, LF_SERVER,
   393                 _("MaxClients (%d) exceeded - dropping connection"),
   394                 MaxClients);
   395         if (MaxClients == 1) {
   396           text = g_strdup_printf(
   397                                   /* Message sent to a player if the
   398                                      server is full */
   399                                   _("Sorry, but this server has a limit of "
   400                                    "1 player, which has been reached.^"
   401                                    "Please try connecting again later."));
   402         } else {
   403           text = g_strdup_printf(
   404                                   /* Message sent to a player if the
   405                                      server is full */
   406                                   _("Sorry, but this server has a limit of "
   407                                    "%d players, which has been reached.^"
   408                                    "Please try connecting again later."),
   409                                   MaxClients);
   410         }
   411         SendServerMessage(NULL, C_NONE, C_PRINTMESSAGE, Play, text);
   412         g_free(text);
   413         /* Make sure they do actually disconnect, eventually! */
   414         if (ConnectTimeout) {
   415           Play->ConnectTimeout = time(NULL) + (time_t) ConnectTimeout;
   416         }
   417       }
   418     } else {
   419       /* A player changed their name during the game (unusual, and not
   420          really properly supported anyway) - notify all players of the
   421          change */
   422       dopelog(2, LF_SERVER, _("%s will now be known as %s"),
   423               GetPlayerName(Play), Data);
   424       BroadcastToClients(C_NONE, C_RENAME, Data, Play, Play);
   425       SetPlayerName(Play, Data);
   426     }
   427     break;
   428   case C_WANTQUIT:
   429     if (Play->EventNum != E_FINISH) {
   430       FinishGame(Play, NULL);
   431     }
   432     break;
   433   case C_REQUESTJET:
   434     i = atoi(Data);
   435     /* Make sure value is within range */
   436     if (i < 0 || i >= NumLocation) {
   437       dopelog(3, LF_SERVER, _("%s: DENIED jet to invalid location %s"),
   438               GetPlayerName(Play), Data);
   439       break;
   440     }
   441     if (Play->EventNum == E_FIGHT || Play->EventNum == E_FIGHTASK) {
   442       if (CanRunHere(Play)) {
   443         break;
   444       } else {
   445         RunFromCombat(Play, i);
   446       }
   447       if (Play->EventNum == E_WAITDONE) {
   448         Play->EventNum = Play->ResyncNum;
   449         SendEvent(Play);
   450       }
   451     }
   452     if (NumTurns > 0 && Play->Turn >= NumTurns
   453         && Play->EventNum != E_FINISH) {
   454       /* Message displayed when a player reaches their maximum number of
   455          turns */
   456       FinishGame(Play, _("Your dealing time is up..."));
   457     } else if (i != Play->IsAt && (NumTurns == 0 || Play->Turn < NumTurns)
   458                && Play->EventNum == E_NONE && Play->Health > 0) {
   459       dopelog(4, LF_SERVER, "%s jets to %s",
   460               GetPlayerName(Play), Location[i].Name);
   461       Play->IsAt = i;
   462       Play->Turn++;
   463       g_date_add_days(Play->date, 1);
   464       Play->Debt = Play->Debt * (DebtInterest + 100) / 100;
   465       Play->Debt = MAX(Play->Debt, 0);
   466       Play->Bank = Play->Bank * (BankInterest + 100) / 100;
   467       Play->Bank = MAX(Play->Bank, 0);
   468       SendPlayerData(Play);
   469       Play->EventNum = E_SUBWAY;
   470       SendEvent(Play);
   471     } else {
   472       /* A player has tried to jet to a new location, but we don't allow
   473          them to. (e.g. they're still fighting someone, or they're
   474          supposed to be dead) */
   475       dopelog(3, LF_SERVER, _("%s: DENIED jet to %s"),
   476               GetPlayerName(Play), Location[i].Name);
   477     }
   478     break;
   479   case C_REQUESTSCORE:
   480     SendHighScores(Play, FALSE, NULL);
   481     break;
   482   case C_CONTACTSPY:
   483     for (list = FirstServer; list; list = g_slist_next(list)) {
   484       tmp = (Player *)list->data;
   485       i = GetListEntry(&(tmp->SpyList), Play);
   486       if (tmp != Play && i >= 0 && tmp->SpyList.Data[i].Turns >= 0) {
   487         SendSpyReport(Play, tmp);
   488       }
   489     }
   490     break;
   491   case C_DEPOSIT:
   492     money = strtoprice(Data);
   493     if (Play->EventNum == E_BANK && Play->Bank + money >= 0
   494         && Play->Cash - money >= 0) {
   495       Play->Bank += money;
   496       Play->Cash -= money;
   497       SendPlayerData(Play);
   498     }
   499     break;
   500   case C_PAYLOAN:
   501     money = strtoprice(Data);
   502     if (Play->EventNum == E_LOANSHARK && money > 0
   503         && Play->Debt - money >= 0 && Play->Cash - money >= 0) {
   504       Play->Debt -= money;
   505       Play->Cash -= money;
   506       SendPlayerData(Play);
   507     }
   508     break;
   509   case C_BUYOBJECT:
   510     BuyObject(Play, Data);
   511     break;
   512   case C_FIGHTACT:
   513     if (Data[0] == 'R')
   514       RunFromCombat(Play, -1);
   515     else
   516       Fire(Play);
   517     break;
   518   case C_ANSWER:
   519     HandleAnswer(Play, To, Data);
   520     break;
   521   case C_DONE:
   522     if (Play->EventNum == E_WAITDONE) {
   523       Play->EventNum = Play->ResyncNum;
   524       SendEvent(Play);
   525     } else if (Play->EventNum != E_NONE && Play->EventNum < E_OUTOFSYNC) {
   526       Play->EventNum++;
   527       SendEvent(Play);
   528     }
   529     break;
   530   case C_SPYON:
   531     if (Play->Cash >= Prices.Spy) {
   532       dopelog(3, LF_SERVER, _("%s now spying on %s"), GetPlayerName(Play),
   533               GetPlayerName(To));
   534       Play->Cash -= Prices.Spy;
   535       LoseBitch(Play, NULL, NULL);
   536       NewEntry.Play = Play;
   537       NewEntry.Turns = -1;
   538       AddListEntry(&(To->SpyList), &NewEntry);
   539       SendPlayerData(Play);
   540     } else {
   541       dopelog(2, LF_SERVER, _("%s spy on %s: DENIED"), GetPlayerName(Play),
   542                 GetPlayerName(To));
   543     }
   544     break;
   545   case C_TIPOFF:
   546     if (Play->Cash >= Prices.Tipoff) {
   547       dopelog(3, LF_SERVER, _("%s tipped off the cops to %s"),
   548               GetPlayerName(Play), GetPlayerName(To));
   549       Play->Cash -= Prices.Tipoff;
   550       LoseBitch(Play, NULL, NULL);
   551       NewEntry.Play = Play;
   552       NewEntry.Turns = 0;
   553       AddListEntry(&(To->TipList), &NewEntry);
   554       SendPlayerData(Play);
   555     } else {
   556       g_warning(_("%s tipoff about %s: DENIED"), GetPlayerName(Play),
   557                 GetPlayerName(To));
   558     }
   559     break;
   560   case C_SACKBITCH:
   561     if (Play->Bitches.Carried > 0) {
   562       LoseBitch(Play, NULL, NULL);
   563       SendPlayerData(Play);
   564     }
   565     break;
   566   case C_MSG:
   567     if (Network)
   568       dopelog(3, LF_SERVER, "%s: %s", GetPlayerName(Play), Data);
   569     BroadcastToClients(C_NONE, C_MSG, Data, Play, Play);
   570     break;
   571   default:
   572     dopelog(0, LF_SERVER, _("Unknown message: %s:%c:%s:%s"),
   573             GetPlayerName(Play), Code, GetPlayerName(To), Data);
   574     break;
   575   }
   576 }
   577 
   578 /* 
   579  * Notifies all clients that player "Play" has left the game and
   580  * cleans up after them if necessary.
   581  */
   582 void ClientLeftServer(Player *Play)
   583 {
   584   Player *tmp;
   585   GSList *list;
   586 
   587   if (!IsConnectedPlayer(Play))
   588     return;
   589 
   590   if (Play->EventNum == E_FIGHT || Play->EventNum == E_FIGHTASK) {
   591     WithdrawFromCombat(Play);
   592   }
   593   for (list = FirstServer; list; list = g_slist_next(list)) {
   594     tmp = (Player *)list->data;
   595     if (tmp != Play) {
   596       RemoveAllEntries(&(tmp->TipList), Play);
   597       RemoveAllEntries(&(tmp->SpyList), Play);
   598     }
   599   }
   600   BroadcastToClients(C_NONE, C_LEAVE, GetPlayerName(Play), Play, Play);
   601 }
   602 
   603 /* 
   604  * Closes down the server and frees up associated handles and memory.
   605  */
   606 void CleanUpServer()
   607 {
   608   while (FirstServer) {
   609     FirstServer = RemovePlayer((Player *)FirstServer->data, FirstServer);
   610   }
   611 #ifdef NETWORKING
   612   if (Server)
   613     CloseSocket(ListenSock);
   614 #endif
   615 }
   616 
   617 /* 
   618  * Responds to a SIGUSR1 signal, and requests the main event loop to
   619  * reregister the server with the dopewars metaserver.
   620  */
   621 void ReregisterHandle(int sig)
   622 {
   623   ReregisterRequest = 1;
   624 }
   625 
   626 /* 
   627  * Responds to a SIGHUP signal, and requests the main event loop to
   628  * close and then reopen the log file (if any).
   629  */
   630 void RelogHandle(int sig)
   631 {
   632   RelogRequest = 1;
   633 }
   634 
   635 /* 
   636  * Traps an attempt by the user to send dopewars a SIGTERM or SIGINT
   637  * (e.g. pressing Ctrl-C) and signals for a "nice" shutdown. Restores
   638  * the default signal action (to terminate without cleanup) so that
   639  * the user can still close the program easily if this cleanup code
   640  * then causes problems or long delays.
   641  */
   642 void BreakHandle(int sig)
   643 {
   644   struct sigaction sact;
   645 
   646   TerminateRequest = 1;
   647   sact.sa_handler = SIG_DFL;
   648   sact.sa_flags = 0;
   649   sigemptyset(&sact.sa_mask);
   650   sigaction(SIGTERM, &sact, NULL);
   651   sigaction(SIGINT, &sact, NULL);
   652 }
   653 
   654 /* 
   655  * Prints the server help screen to the given file pointer.
   656  */
   657 void PrintHelpTo(FILE *fp)
   658 {
   659   int i;
   660   GString *VarName;
   661 
   662   VarName = g_string_new("");
   663   fprintf(fp, _(HelpText), VERSION);
   664   for (i = 0; i < NUMGLOB; i++) {
   665     if (Globals[i].NameStruct[0]) {
   666       g_string_printf(VarName, "%s%s.%s", Globals[i].NameStruct,
   667                        Globals[i].StructListPt ? "[x]" : "",
   668                        Globals[i].Name);
   669     } else {
   670       g_string_assign(VarName, Globals[i].Name);
   671     }
   672     fprintf(fp, "%-26s %s\n", VarName->str, _(Globals[i].Help));
   673   }
   674   fputs("\n\n", fp);
   675   g_string_free(VarName, TRUE);
   676 }
   677 
   678 /* 
   679  * Displays a simple help screen listing the server commands and options.
   680  */
   681 void ServerHelp(void)
   682 {
   683   int i;
   684   GString *VarName;
   685 
   686   VarName = g_string_new("");
   687   g_print(_(HelpText), VERSION);
   688   for (i = 0; i < NUMGLOB; i++) {
   689     if (Globals[i].NameStruct[0]) {
   690       g_string_printf(VarName, "%s%s.%s", Globals[i].NameStruct,
   691                        Globals[i].StructListPt ? "[x]" : "",
   692                        Globals[i].Name);
   693     } else {
   694       g_string_assign(VarName, Globals[i].Name);
   695     }
   696     g_print("%-26s\t%s\n", VarName->str, _(Globals[i].Help));
   697   }
   698   g_string_free(VarName, TRUE);
   699 }
   700 
   701 #ifdef NETWORKING
   702 static NetworkBuffer *reply_netbuf;
   703 static void ServerReply(const gchar *msg)
   704 {
   705   int msglen;
   706   gchar *msgcp;
   707 
   708   if (reply_netbuf) {
   709     msglen = strlen(msg);
   710     while (msglen > 0 && msg[msglen - 1] == '\n')
   711       msglen--;
   712     if (msglen > 0) {
   713       msgcp = g_strndup(msg, msglen);
   714       QueueMessageForSend(reply_netbuf, msgcp);
   715       g_free(msgcp);
   716     }
   717   } else {
   718     g_print("%s", msg);
   719   }
   720 }
   721 
   722 /* 
   723  * Creates a pid file (if "PidFile" is non-NULL) and writes the process
   724  * ID into it.
   725  */
   726 void CreatePidFile(void)
   727 {
   728   FILE *fp;
   729 
   730   if (!PidFile)
   731     return;
   732   fp = fopen(PidFile, "w");
   733   if (fp) {
   734     dopelog(1, LF_SERVER, _("Maintaining pid file %s"), PidFile);
   735     fprintf(fp, "%ld\n", (long)getpid());
   736     fclose(fp);
   737     chmod(PidFile, S_IREAD | S_IWRITE);
   738   } else {
   739     gchar *OpenError = ErrStrFromErrno(errno);
   740     g_warning(_("Cannot create pid file %s: %s"), PidFile, OpenError);
   741     g_free(OpenError);
   742   }
   743 }
   744 
   745 /* 
   746  * Removes the previously-created pid file "PidFile".
   747  */
   748 void RemovePidFile(void)
   749 {
   750   if (PidFile)
   751     unlink(PidFile);
   752 }
   753 
   754 static gboolean StartServer(void)
   755 {
   756   LastError *sockerr = NULL;
   757   GString *errstr;
   758 
   759 #ifndef CYGWIN
   760   struct sigaction sact;
   761 #endif
   762 
   763   if (!CheckHighScoreFileConfig())
   764     return FALSE;
   765   Scanner = g_scanner_new(&ScannerConfig);
   766   Scanner->msg_handler = ScannerErrorHandler;
   767   Scanner->input_name = "(stdin)";
   768 
   769   /* Make the output line-buffered, so that the log file (if used) is
   770    * updated regularly */
   771   fflush(stdout);
   772 
   773 #ifdef SETVBUF_REVERSED         /* 2nd and 3rd arguments are reversed on
   774                                  * some systems */
   775   setvbuf(stdout, _IOLBF, (char *)NULL, 0);
   776 #else
   777   setvbuf(stdout, (char *)NULL, _IOLBF, 0);
   778 #endif
   779 
   780   Network = Server = TRUE;
   781   FirstServer = NULL;
   782   ClientMessageHandlerPt = NULL;
   783   ListenSock = CreateTCPSocket(&sockerr);
   784   if (ListenSock == SOCKET_ERROR) {
   785     errstr = g_string_new("");
   786     g_string_assign_error(errstr, sockerr);
   787     g_log(NULL, G_LOG_LEVEL_CRITICAL,
   788           _("Cannot create server (listening) socket (%s) Aborting."),
   789           errstr->str);
   790     g_string_free(errstr, TRUE);
   791     FreeError(sockerr);
   792     exit(EXIT_FAILURE);
   793   }
   794 
   795   /* This doesn't seem to work properly under Win32 */
   796 #ifndef CYGWIN
   797   SetReuse(ListenSock);
   798 #endif
   799 
   800   SetBlocking(ListenSock, FALSE);
   801 
   802   if (!BindTCPSocket(ListenSock, BindAddress, Port, &sockerr)) {
   803     errstr = g_string_new("");
   804     g_string_assign_error(errstr, sockerr);
   805     g_log(NULL, G_LOG_LEVEL_CRITICAL,
   806           _("Cannot bind to port %u (%s) Aborting."), Port, errstr->str);
   807     g_string_free(errstr, TRUE);
   808     FreeError(sockerr);
   809     exit(EXIT_FAILURE);
   810   }
   811 
   812   if (listen(ListenSock, 10) == SOCKET_ERROR) {
   813     g_log(NULL, G_LOG_LEVEL_CRITICAL,
   814           _("Cannot listen to network socket. Aborting."));
   815     exit(EXIT_FAILURE);
   816   }
   817 
   818   /* Initial startup message for the server */
   819   dopelog(0, LF_SERVER, 
   820           _("dopewars server version %s ready and waiting for "
   821             "connections on port %d."), VERSION, Port);
   822 
   823   MetaUpdateTimeout = MetaMinTimeout = 0;
   824 
   825   TerminateRequest = ReregisterRequest = RelogRequest = 0;
   826 
   827 #if !CYGWIN
   828   sact.sa_handler = ReregisterHandle;
   829   sact.sa_flags = 0;
   830   sigemptyset(&sact.sa_mask);
   831   if (sigaction(SIGUSR1, &sact, NULL) == -1) {
   832     /* Warning messages displayed if we fail to trap various signals */
   833     g_warning(_("Cannot install SIGUSR1 interrupt handler!"));
   834   }
   835   sact.sa_handler = RelogHandle;
   836   sact.sa_flags = 0;
   837   sigemptyset(&sact.sa_mask);
   838   if (sigaction(SIGHUP, &sact, NULL) == -1) {
   839     g_warning(_("Cannot install SIGHUP interrupt handler!"));
   840   }
   841   sact.sa_handler = BreakHandle;
   842   sact.sa_flags = 0;
   843   sigemptyset(&sact.sa_mask);
   844   if (sigaction(SIGINT, &sact, NULL) == -1) {
   845     g_warning(_("Cannot install SIGINT interrupt handler!"));
   846   }
   847   if (sigaction(SIGTERM, &sact, NULL) == -1) {
   848     g_warning(_("Cannot install SIGTERM interrupt handler!"));
   849   }
   850   sact.sa_handler = SIG_IGN;
   851   sact.sa_flags = 0;
   852   if (sigaction(SIGPIPE, &sact, NULL) == -1) {
   853     g_warning(_("Cannot install pipe handler!"));
   854   }
   855 #endif
   856   return TRUE;
   857 }
   858 
   859 static void InitMetaServer()
   860 {
   861   CurlInit(&MetaConn);
   862 #ifdef GUI_SERVER
   863   SetCurlCallback(&MetaConn, glib_timeout, glib_socket);
   864 #endif
   865   RegisterWithMetaServer(TRUE, TRUE, FALSE);
   866 }
   867 
   868 /* 
   869  * Begin the process of shutting down the server. In order to do this,
   870  * we need to log out all of the currently connected players, and tell
   871  * the metaserver that we're shutting down. We only shut down properly
   872  * once all of these messages have been completely sent and
   873  * acknowledged. (Of course, this can be overridden by a SIGINT or
   874  * similar in the case of unresponsive players.)
   875  */
   876 void RequestServerShutdown(void)
   877 {
   878   RegisterWithMetaServer(FALSE, FALSE, FALSE);
   879   BroadcastToClients(C_NONE, C_QUIT, NULL, NULL, NULL);
   880   WantQuit = TRUE;
   881 }
   882 
   883 /* 
   884  * Returns TRUE if the actions initiated by RequestServerShutdown()
   885  * have been successfully completed, such that we can shut down the
   886  * server properly now.
   887  */
   888 gboolean IsServerShutdown(void)
   889 {
   890   return (WantQuit && !FirstServer && !MetaConn.running);
   891 }
   892 
   893 static GPrintFunc StartServerReply(NetworkBuffer *netbuf)
   894 {
   895   reply_netbuf = netbuf;
   896   if (netbuf)
   897     return g_set_print_handler(ServerReply);
   898   else
   899     return NULL;
   900 }
   901 
   902 static void FinishServerReply(GPrintFunc oldprint)
   903 {
   904   if (oldprint)
   905     g_set_print_handler(oldprint);
   906 }
   907 
   908 static void ServerSaveConfigFile(const char *string)
   909 {
   910   gchar *file = NULL;
   911 
   912   if (!string) {
   913     file = GetLocalConfigFile();
   914     string = file;
   915   }
   916   if (UpdateConfigFile(file, FALSE)) {
   917     g_print(_("Configuration file saved OK as %s\n"), string);
   918   }
   919   g_free(file);
   920 }
   921 
   922 static void HandleServerCommand(char *string, NetworkBuffer *netbuf,
   923                                 gboolean ForceUTF8)
   924 {
   925   GSList *list;
   926   Player *tmp;
   927   GPrintFunc oldprint;
   928   Converter *conv;
   929 
   930   oldprint = StartServerReply(netbuf);
   931 
   932   conv = Conv_New();
   933   if (ForceUTF8) {
   934     Conv_SetCodeset(conv, "UTF-8");
   935   }
   936   g_scanner_input_text(Scanner, string, strlen(string));
   937   if (!ParseNextConfig(Scanner, conv, NULL, TRUE)) {
   938     if (g_ascii_strncasecmp(string, "help", 4) == 0 || g_ascii_strncasecmp(string, "h", 1) == 0
   939         || strcmp(string, "?") == 0) {
   940       ServerHelp();
   941     } else if (g_ascii_strncasecmp(string, "quit", 4) == 0) {
   942       RequestServerShutdown();
   943     } else if (g_ascii_strncasecmp(string, "msg:", 4) == 0) {
   944       BroadcastToClients(C_NONE, C_MSG, string + 4, NULL, NULL);
   945     } else if (g_ascii_strncasecmp(string, "save ", 5) == 0) {
   946       ServerSaveConfigFile(string + 5);
   947     } else if (g_ascii_strncasecmp(string, "save", 4) == 0) {
   948       ServerSaveConfigFile(NULL);
   949     } else if (g_ascii_strncasecmp(string, "list", 4) == 0) {
   950       if (FirstServer) {
   951         g_print(_("Users currently logged on:-\n"));
   952         for (list = FirstServer; list; list = g_slist_next(list)) {
   953           tmp = (Player *)list->data;
   954           if (!IsCop(tmp)) {
   955             g_print("%s\n", GetPlayerName(tmp));
   956           }
   957         }
   958       } else
   959         g_print(_("No users currently logged on!\n"));
   960     } else if (g_ascii_strncasecmp(string, "push ", 5) == 0) {
   961       tmp = GetPlayerByName(string + 5, FirstServer);
   962       if (tmp) {
   963         g_print(_("Pushing %s\n"), GetPlayerName(tmp));
   964         SendServerMessage(NULL, C_NONE, C_PUSH, tmp, NULL);
   965       } else
   966         g_print(_("No such user!\n"));
   967     } else if (g_ascii_strncasecmp(string, "kill ", 5) == 0) {
   968       tmp = GetPlayerByName(string + 5, FirstServer);
   969       if (tmp) {
   970         /* The named user has been removed from the server following
   971            a "kill" command */
   972         g_print(_("%s killed\n"), GetPlayerName(tmp));
   973         BroadcastToClients(C_NONE, C_KILL, GetPlayerName(tmp), tmp,
   974                            (Player *)FirstServer->data);
   975         FirstServer = RemovePlayer(tmp, FirstServer);
   976       } else
   977         g_print(_("No such user!\n"));
   978     } else {
   979       g_print(_("Unknown command - try \"help\" for help...\n"));
   980     }
   981   }
   982   Conv_Free(conv);
   983   FinishServerReply(oldprint);
   984 }
   985 
   986 Player *HandleNewConnection(void)
   987 {
   988   socklen_t cadsize;
   989   int ClientSock;
   990   struct sockaddr_in ClientAddr;
   991   Player *tmp;
   992   cadsize = sizeof(struct sockaddr);
   993   if ((ClientSock = accept(ListenSock, (struct sockaddr *)&ClientAddr,
   994                             &cadsize)) == -1) {
   995     perror("accept socket");
   996     exit(EXIT_FAILURE);
   997   }
   998   dopelog(2, LF_SERVER, _("got connection from %s"),
   999           inet_ntoa(ClientAddr.sin_addr));
  1000   tmp = g_new(Player, 1);
  1001 
  1002   FirstServer = AddPlayer(ClientSock, tmp, FirstServer);
  1003   if (ConnectTimeout) {
  1004     tmp->ConnectTimeout = time(NULL) + (time_t) ConnectTimeout;
  1005   }
  1006   return tmp;
  1007 }
  1008 
  1009 void StopServer()
  1010 {
  1011   dopelog(0, LF_SERVER, _("dopewars server terminating."));
  1012   g_scanner_destroy(Scanner);
  1013   CleanUpServer();
  1014   RemovePidFile();
  1015 }
  1016 
  1017 void RemovePlayerFromServer(Player *Play)
  1018 {
  1019   if (!WantQuit && strlen(GetPlayerName(Play)) > 0) {
  1020     dopelog(2, LF_SERVER, _("%s leaves the server!"), GetPlayerName(Play));
  1021     ClientLeftServer(Play);
  1022     /* Blank the name, so that CountPlayers ignores this player */
  1023     SetPlayerName(Play, NULL);
  1024     /* Report the new high scores (if any) and the new number of players
  1025      * to the metaserver */
  1026     RegisterWithMetaServer(TRUE, TRUE, TRUE);
  1027   }
  1028   FirstServer = RemovePlayer(Play, FirstServer);
  1029 }
  1030 
  1031 #ifndef CYGWIN
  1032 static gchar sockpref[] = "/tmp/.dopewars";
  1033 
  1034 static gchar *GetLocalSockDir(void)
  1035 {
  1036   return g_strdup_printf("%s-%u", sockpref, Port);
  1037 }
  1038 
  1039 gchar *GetLocalSocket(void)
  1040 {
  1041   return g_strdup_printf("%s-%u/socket", sockpref, Port);
  1042 }
  1043 
  1044 static void CloseLocalSocket(int localsock)
  1045 {
  1046   gchar *sockname, *sockdir;
  1047 
  1048   if (localsock >= 0)
  1049     close(localsock);
  1050 
  1051   sockname = GetLocalSocket();
  1052   sockdir = GetLocalSockDir();
  1053   unlink(sockname);
  1054   rmdir(sockdir);
  1055   g_free(sockname);
  1056   g_free(sockdir);
  1057 }
  1058 
  1059 static int SetupLocalSocket(void)
  1060 {
  1061   int sock;
  1062   struct sockaddr_un addr;
  1063   gchar *sockname, *sockdir;
  1064 
  1065   CloseLocalSocket(-1);
  1066 
  1067   sock = socket(PF_UNIX, SOCK_STREAM, 0);
  1068   if (sock == -1)
  1069     return -1;
  1070 
  1071   SetBlocking(sock, FALSE);
  1072 
  1073   sockname = GetLocalSocket();
  1074   sockdir = GetLocalSockDir();
  1075   if (mkdir(sockdir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
  1076     return -1;
  1077 
  1078   addr.sun_family = AF_UNIX;
  1079   strncpy(addr.sun_path, sockname, sizeof(addr.sun_path));
  1080   addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
  1081 
  1082   if (bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1)
  1083     return -1;
  1084 
  1085   chmod(sockname, S_IRUSR | S_IWUSR);
  1086   g_free(sockname);
  1087   g_free(sockdir);
  1088 
  1089   listen(sock, 10);
  1090 
  1091   return sock;
  1092 }
  1093 #endif
  1094 
  1095 static void LogMetaReply(CurlConnection *conn)
  1096 {
  1097   g_ptr_array_foreach(conn->headers, log_meta_headers, NULL);
  1098   char *ch = conn->data;
  1099   while(ch && *ch) {
  1100     char *nextch = CurlNextLine(conn, ch);
  1101     if (*ch)
  1102       dopelog(2, LF_SERVER, _("MetaServer: %s"), ch);
  1103     ch = nextch;
  1104   }
  1105   dopelog(4, LF_SERVER, _("MetaServer: (closed)"));
  1106 }
  1107 
  1108 #ifdef GUI_SERVER
  1109 static gboolean glib_timeout(gpointer userp)
  1110 {
  1111   CurlConnection *g = userp;
  1112   int still_running;
  1113   GError *err = NULL;
  1114   if (!CurlConnectionSocketAction(g, CURL_SOCKET_TIMEOUT, 0, &still_running,
  1115                                   &err)) {
  1116     MetaConnectError(g, err);
  1117     g_error_free(err);
  1118   }
  1119   g->timer_event = 0;
  1120   return G_SOURCE_REMOVE;
  1121 }
  1122 
  1123 static void GuiQuitServer()
  1124 {
  1125   gtk_main_quit();
  1126   StopServer();
  1127 }
  1128 
  1129 /* Called by glib when we get action on a multi socket */
  1130 static gboolean glib_socket(GIOChannel *ch, GIOCondition condition,
  1131                             gpointer data)
  1132 {
  1133   CurlConnection *g = (CurlConnection*) data;
  1134   int still_running;
  1135   GError *err = NULL;
  1136   int fd = g_io_channel_unix_get_fd(ch);
  1137   int action =
  1138     ((condition & G_IO_IN) ? CURL_CSELECT_IN : 0) |
  1139     ((condition & G_IO_OUT) ? CURL_CSELECT_OUT : 0);
  1140 
  1141   if (!CurlConnectionSocketAction(g, fd, action, &still_running, &err)) {
  1142     MetaConnectError(g, err);
  1143     g_error_free(err);
  1144   }
  1145   if (still_running) {
  1146     return TRUE;
  1147   } else {
  1148     if (g->timer_event) {
  1149       dp_g_source_remove(g->timer_event);
  1150       g->timer_event = 0;
  1151     }
  1152     LogMetaReply(g);
  1153     CloseCurlConnection(g);
  1154     if (IsServerShutdown())
  1155       GuiQuitServer();
  1156     return FALSE;
  1157   }
  1158 }
  1159 
  1160 #endif
  1161 
  1162 /* 
  1163  * Initializes server, processes network and interactive messages, and
  1164  * finally cleans up the server on exit.
  1165  */
  1166 void ServerLoop(struct CMDLINE *cmdline)
  1167 {
  1168   Player *tmp;
  1169   GSList *list, *listcp;
  1170   fd_set readfs, writefs, errorfs;
  1171   int topsock;
  1172   struct timeval timeout;
  1173   int MinTimeout;
  1174   GString *LineBuf;
  1175 
  1176   gboolean DoneOK;
  1177 
  1178 #ifndef CYGWIN
  1179   gchar *buf;
  1180   int localsock;
  1181   GSList *nextlist;
  1182   GPrintFunc oldprint;
  1183   GSList *localconn = NULL;
  1184 #endif
  1185 
  1186   InitConfiguration(cmdline);
  1187 
  1188   if (!StartServer())
  1189     return;
  1190 
  1191 #ifdef HAVE_FORK
  1192   /* Daemonize; continue if the fork was successful and we are the child,
  1193    * or if the fork failed */
  1194   if (Daemonize && fork() > 0)
  1195     return;
  1196 #endif
  1197   CreatePidFile();
  1198   InitMetaServer();
  1199 
  1200 #ifndef CYGWIN
  1201   localsock = SetupLocalSocket();
  1202   if (localsock == -1) {
  1203     dopelog(0, LF_SERVER,
  1204             _("Could not set up Unix domain socket for admin "
  1205               "connections - check permissions on /tmp!"));
  1206   }
  1207 #endif
  1208 
  1209   LineBuf = g_string_new("");
  1210   while (1) {
  1211     FD_ZERO(&readfs);
  1212     FD_ZERO(&writefs);
  1213     FD_ZERO(&errorfs);
  1214     curl_multi_fdset(MetaConn.multi, &readfs, &writefs, &errorfs, &topsock);
  1215     FD_SET(ListenSock, &readfs);
  1216     FD_SET(ListenSock, &errorfs);
  1217     topsock = MAX(topsock + 1, ListenSock + 1);
  1218 #ifndef CYGWIN
  1219     if (localsock >= 0) {
  1220       FD_SET(localsock, &readfs);
  1221       topsock = MAX(topsock, localsock + 1);
  1222     }
  1223     for (list = localconn; list; list = g_slist_next(list)) {
  1224       NetworkBuffer *netbuf;
  1225 
  1226       netbuf = (NetworkBuffer *)list->data;
  1227       SetSelectForNetworkBuffer(netbuf, &readfs, &writefs, &errorfs,
  1228                                 &topsock);
  1229     }
  1230 #endif
  1231     for (list = FirstServer; list; list = g_slist_next(list)) {
  1232       tmp = (Player *)list->data;
  1233       if (!IsCop(tmp)) {
  1234         SetSelectForNetworkBuffer(&tmp->NetBuf, &readfs, &writefs,
  1235                                   &errorfs, &topsock);
  1236       }
  1237     }
  1238     MinTimeout = GetMinimumTimeout(FirstServer);
  1239     if (MinTimeout != -1) {
  1240       timeout.tv_sec = MinTimeout;
  1241       timeout.tv_usec = 0;
  1242     }
  1243     if (select(topsock, &readfs, &writefs, &errorfs,
  1244                MinTimeout == -1 ? NULL : &timeout) == -1) {
  1245       if (errno == EINTR) {
  1246         if (ReregisterRequest) {
  1247           ReregisterRequest = 0;
  1248           RegisterWithMetaServer(TRUE, TRUE, FALSE);
  1249           continue;
  1250         } else if (TerminateRequest) {
  1251           TerminateRequest = 0;
  1252           RequestServerShutdown();
  1253           if (IsServerShutdown())
  1254             break;
  1255           else
  1256             continue;
  1257         } else if (RelogRequest) {      /* Re-open log file */
  1258           RelogRequest = 0;
  1259           CloseLog();
  1260           OpenLog();
  1261           continue;
  1262         } else
  1263           continue;
  1264       }
  1265       perror("select");
  1266       break;
  1267     }
  1268     FirstServer = HandleTimeouts(FirstServer);
  1269     if (FD_ISSET(ListenSock, &readfs)) {
  1270       HandleNewConnection();
  1271     }
  1272 #ifndef CYGWIN
  1273     if (localsock >= 0 && FD_ISSET(localsock, &readfs)) {
  1274       int newlocal;
  1275       NetworkBuffer *netbuf;
  1276 
  1277       newlocal = accept(localsock, NULL, NULL);
  1278       netbuf = g_new(NetworkBuffer, 1);
  1279 
  1280       InitNetworkBuffer(netbuf, '\n', '\r', NULL);
  1281       BindNetworkBufferToSocket(netbuf, newlocal);
  1282       localconn = g_slist_append(localconn, netbuf);
  1283       oldprint = StartServerReply(netbuf);
  1284       g_print(_("dopewars server version %s ready for admin commands; "
  1285                 "try \"help\" for help"), VERSION);
  1286       FinishServerReply(oldprint);
  1287       dopelog(1, LF_SERVER, _("New admin connection"));
  1288     }
  1289     list = localconn;
  1290     while (list) {
  1291       NetworkBuffer *netbuf;
  1292 
  1293       nextlist = g_slist_next(list);
  1294       netbuf = (NetworkBuffer *)list->data;
  1295       if (netbuf) {
  1296         if (RespondToSelect(netbuf, &readfs, &writefs, &errorfs, &DoneOK)) {
  1297           while ((buf = GetWaitingMessage(netbuf)) != NULL) {
  1298             dopelog(2, LF_SERVER, _("Admin command: %s"), buf);
  1299             HandleServerCommand(buf, netbuf, FALSE);
  1300             g_free(buf);
  1301           }
  1302         }
  1303         if (!DoneOK) {
  1304           dopelog(1, LF_SERVER, _("Admin connection closed"));
  1305           localconn = g_slist_remove(localconn, netbuf);
  1306           ShutdownNetworkBuffer(netbuf);
  1307           g_free(netbuf);
  1308         }
  1309         list = nextlist;
  1310       }
  1311     }
  1312     if (IsServerShutdown())
  1313       break;
  1314 #endif
  1315     if (MetaConn.running) {
  1316       GError *tmp_error = NULL;
  1317       int still_running;
  1318       if (!CurlConnectionPerform(&MetaConn, &still_running, &tmp_error)) {
  1319         MetaConnectError(&MetaConn, tmp_error);
  1320         g_error_free(tmp_error);
  1321         if (IsServerShutdown())
  1322           break;
  1323       } else if (still_running == 0) {
  1324         LogMetaReply(&MetaConn);
  1325         CloseCurlConnection(&MetaConn);
  1326         if (IsServerShutdown())
  1327           break;
  1328       }
  1329     }
  1330 
  1331     /* Check all players for data; iterate over a copy of the player list,
  1332      * as HandleServerPlayer may remove players from this list! */
  1333     listcp = g_slist_copy(FirstServer);
  1334     for (list = listcp; list; list = g_slist_next(list)) {
  1335       if (list->data && g_slist_find(FirstServer, list->data)) {
  1336         tmp = (Player *)list->data;
  1337         if (RespondToSelect(&tmp->NetBuf, &readfs, &writefs,
  1338                             &errorfs, &DoneOK)) {
  1339           /* If any complete messages were read, process them */
  1340           HandleServerPlayer(tmp);
  1341         }
  1342         if (!DoneOK) {
  1343           /* The socket has been shut down, or the buffer was filled -
  1344            * remove player */
  1345           RemovePlayerFromServer(tmp);
  1346           if (IsServerShutdown()) {
  1347             break;
  1348           }
  1349         }
  1350       }
  1351     }
  1352     g_slist_free(listcp);
  1353     if (IsServerShutdown()) {
  1354       break;
  1355     }
  1356   }
  1357 #ifndef CYGWIN
  1358   CloseLocalSocket(localsock);
  1359 #endif
  1360   StopServer();
  1361   g_string_free(LineBuf, TRUE);
  1362 
  1363   CurlCleanup(&MetaConn);
  1364 }
  1365 
  1366 #ifdef GUI_SERVER
  1367 static GtkWidget *TextOutput;
  1368 static gint ListenTag = 0;
  1369 static void SocketStatus(NetworkBuffer *NetBuf, gboolean Read,
  1370                          gboolean Write, gboolean Exception, gboolean CallNow);
  1371 static void GuiSetTimeouts(void);
  1372 static time_t NextTimeout = 0;
  1373 static guint TimeoutTag = 0;
  1374 
  1375 static gboolean GuiDoTimeouts(gpointer data)
  1376 {
  1377   /* Forget the TimeoutTag so that GuiSetTimeouts doesn't delete it -
  1378    * it'll be deleted automatically anyway when we return FALSE */
  1379   TimeoutTag = 0;
  1380   NextTimeout = 0;
  1381 
  1382   FirstServer = HandleTimeouts(FirstServer);
  1383   GuiSetTimeouts();
  1384   return FALSE;
  1385 }
  1386 
  1387 void GuiSetTimeouts(void)
  1388 {
  1389   int MinTimeout;
  1390   time_t TimeNow;
  1391 
  1392   TimeNow = time(NULL);
  1393   MinTimeout = GetMinimumTimeout(FirstServer);
  1394   if (TimeNow + MinTimeout < NextTimeout || NextTimeout < TimeNow) {
  1395     if (TimeoutTag > 0)
  1396       dp_g_source_remove(TimeoutTag);
  1397     TimeoutTag = 0;
  1398     if (MinTimeout > 0) {
  1399       TimeoutTag = dp_g_timeout_add(MinTimeout * 1000, GuiDoTimeouts, NULL);
  1400       NextTimeout = TimeNow + MinTimeout;
  1401     }
  1402   }
  1403 }
  1404 
  1405 static void GuiServerPrintFunc(const gchar *string)
  1406 {
  1407   TextViewAppend(GTK_TEXT_VIEW(TextOutput), string, NULL, TRUE);
  1408 }
  1409 
  1410 static void GuiServerLogMessage(const gchar *log_domain,
  1411                                 GLogLevelFlags log_level,
  1412                                 const gchar *message, gpointer user_data)
  1413 {
  1414   GString *text;
  1415 
  1416   text = GetLogString(log_level, message);
  1417   if (text) {
  1418     g_string_append(text, "\n");
  1419     GuiServerPrintFunc(text->str);
  1420     g_string_free(text, TRUE);
  1421   }
  1422 }
  1423 
  1424 #ifdef CYGWIN
  1425 static void ServiceFailure(const gchar *log_domain,
  1426                            GLogLevelFlags log_level, const gchar *message,
  1427                            gpointer user_data)
  1428 {
  1429   SERVICE_STATUS status;
  1430 
  1431   g_print("%s\n", message);
  1432   status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
  1433   status.dwCurrentState = SERVICE_STOPPED;
  1434   status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
  1435   status.dwWin32ExitCode = ERROR_NETWORK_UNREACHABLE;
  1436   status.dwCheckPoint = 0;
  1437   status.dwWaitHint = 0;
  1438   SetServiceStatus(scHandle, &status);
  1439 }
  1440 #endif
  1441 
  1442 static void GuiDoCommand(GtkWidget *widget, gpointer data)
  1443 {
  1444   gchar *text;
  1445 
  1446   text = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
  1447   gtk_editable_delete_text(GTK_EDITABLE(widget), 0, -1);
  1448   HandleServerCommand(text, NULL, TRUE);
  1449   g_free(text);
  1450   if (IsServerShutdown())
  1451     GuiQuitServer();
  1452 }
  1453 
  1454 static gboolean GuiHandleSocket(GIOChannel *source, GIOCondition condition,
  1455                                 gpointer data)
  1456 {
  1457   Player *Play;
  1458   gboolean DoneOK;
  1459 
  1460   Play = (Player *)data;
  1461 
  1462   /* Sanity check - is the player still around? */
  1463   if (!g_slist_find(FirstServer, (gpointer)Play))
  1464     return TRUE;
  1465 
  1466   if (PlayerHandleNetwork(Play, condition & G_IO_IN, condition & G_IO_OUT,
  1467                           condition & G_IO_ERR, &DoneOK)) {
  1468     HandleServerPlayer(Play);
  1469     GuiSetTimeouts();           /* We may have set some new timeouts */
  1470   }
  1471   if (!DoneOK) {
  1472     RemovePlayerFromServer(Play);
  1473     if (IsServerShutdown())
  1474       GuiQuitServer();
  1475   }
  1476   return TRUE;
  1477 }
  1478 
  1479 void SocketStatus(NetworkBuffer *NetBuf, gboolean Read, gboolean Write,
  1480                   gboolean Exception, gboolean CallNow)
  1481 {
  1482   if (NetBuf->InputTag)
  1483     dp_g_source_remove(NetBuf->InputTag);
  1484   NetBuf->InputTag = 0;
  1485   if (Read || Write) {
  1486     NetBuf->InputTag = dp_g_io_add_watch(NetBuf->ioch,
  1487                                      (Read ? G_IO_IN : 0) |
  1488                                      (Write ? G_IO_OUT : 0) |
  1489                                      (Exception ? G_IO_ERR : 0),
  1490                                      GuiHandleSocket,
  1491                                      NetBuf->CallBackData);
  1492   }
  1493   if (CallNow)
  1494     GuiHandleSocket(NetBuf->ioch, 0, NetBuf->CallBackData);
  1495 }
  1496 
  1497 static gboolean GuiNewConnect(GIOChannel *source, GIOCondition condition,
  1498                               gpointer data)
  1499 {
  1500   Player *Play;
  1501 
  1502   if (condition & G_IO_IN) {
  1503     Play = HandleNewConnection();
  1504     SetNetworkBufferCallBack(&Play->NetBuf, SocketStatus, (gpointer)Play);
  1505   }
  1506   return TRUE;
  1507 }
  1508 
  1509 static gboolean TriedPoliteShutdown = FALSE;
  1510 
  1511 static gint GuiRequestDelete(GtkWidget *widget, GdkEvent * event,
  1512                              gpointer data)
  1513 {
  1514   if (TriedPoliteShutdown) {
  1515     GuiQuitServer();
  1516   } else {
  1517     TriedPoliteShutdown = TRUE;
  1518     HandleServerCommand("quit", NULL, FALSE);
  1519     if (IsServerShutdown())
  1520       GuiQuitServer();
  1521   }
  1522   return TRUE;                  /* Never allow automatic deletion - we
  1523                                  * handle it manually */
  1524 }
  1525 
  1526 #ifdef CYGWIN
  1527 static HWND mainhwnd = NULL;
  1528 static BOOL systray = FALSE;
  1529 
  1530 static BOOL RegisterStatus(DWORD state)
  1531 {
  1532   SERVICE_STATUS status;
  1533 
  1534   status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
  1535   status.dwCurrentState = state;
  1536   status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
  1537   status.dwWin32ExitCode = NO_ERROR;
  1538   status.dwCheckPoint = 0;
  1539   status.dwWaitHint = 5000;
  1540   return SetServiceStatus(scHandle, &status);
  1541 }
  1542 
  1543 static VOID WINAPI ServiceHandler(DWORD control)
  1544 {
  1545   DWORD state = SERVICE_RUNNING;
  1546 
  1547   switch (control) {
  1548   case SERVICE_CONTROL_STOP:
  1549     state = SERVICE_STOP_PENDING;
  1550     break;
  1551   }
  1552   if (!RegisterStatus(state)) {
  1553     dopelog(0, LF_SERVER, _("Failed to set NT Service status"));
  1554     return;
  1555   }
  1556 
  1557   if (mainhwnd
  1558       && !PostMessage(mainhwnd, MYWM_SERVICE, 0, (LPARAM) control)) {
  1559     dopelog(0, LF_SERVER, _("Failed to post service notification message"));
  1560     return;
  1561   }
  1562 }
  1563 
  1564 static VOID WINAPI ServiceInit(DWORD argc, LPTSTR * argv)
  1565 {
  1566   scHandle = RegisterServiceCtrlHandler("dopewars-server", ServiceHandler);
  1567   if (!scHandle) {
  1568     dopelog(0, LF_SERVER, _("Failed to register service handler"));
  1569     return;
  1570   }
  1571   if (!RegisterStatus(SERVICE_START_PENDING)) {
  1572     dopelog(0, LF_SERVER, _("Failed to set NT Service status"));
  1573     return;
  1574   }
  1575 
  1576   GuiServerLoop(NULL, TRUE);
  1577 
  1578   if (!RegisterStatus(SERVICE_STOPPED)) {
  1579     dopelog(0, LF_SERVER, _("Failed to set NT Service status"));
  1580     return;
  1581   }
  1582 }
  1583 
  1584 void ServiceMain(struct CMDLINE *cmdline)
  1585 {
  1586   SERVICE_TABLE_ENTRY services[] = {
  1587     {"dopewars-server", ServiceInit},
  1588     {NULL, NULL}
  1589   };
  1590 
  1591   InitConfiguration(cmdline);
  1592 
  1593   if (!StartServiceCtrlDispatcher(services)) {
  1594     dopelog(0, LF_SERVER, _("Failed to start NT Service"));
  1595   }
  1596 }
  1597 
  1598 static LRESULT CALLBACK GuiServerWndProc(HWND hwnd, UINT msg,
  1599                                          WPARAM wparam, LPARAM lparam)
  1600 {
  1601   if (hwnd == mainhwnd)
  1602     switch (msg) {
  1603     case MYWM_SERVICE:
  1604       if (lparam == SERVICE_CONTROL_STOP) {
  1605         RequestServerShutdown();
  1606       }
  1607       break;
  1608     case MYWM_TASKBAR:
  1609       if ((UINT) lparam == WM_LBUTTONDOWN)
  1610         ShowWindow(mainhwnd, SW_SHOW);
  1611       break;
  1612     case WM_SYSCOMMAND:
  1613       if (wparam == SC_MINIMIZE && systray) {
  1614         ShowWindow(mainhwnd, SW_HIDE);
  1615         return TRUE;
  1616       }
  1617       break;
  1618     }
  1619   return FALSE;
  1620 }
  1621 
  1622 static void SetupTaskBarIcon(GtkWidget *widget)
  1623 {
  1624   NOTIFYICONDATA nid;
  1625 
  1626   nid.cbSize = sizeof(NOTIFYICONDATA);
  1627   nid.uID = 1000;
  1628   if (widget && !widget->hWnd)
  1629     return;
  1630   if (!widget && !mainhwnd)
  1631     return;
  1632 
  1633   nid.hWnd = mainhwnd;
  1634   if (widget && MinToSysTray) {
  1635     nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
  1636     nid.uCallbackMessage = MYWM_TASKBAR;
  1637     nid.hIcon = mainIcon;
  1638     strcpy(nid.szTip, "dopewars server - running");
  1639     systray = Shell_NotifyIcon(NIM_ADD, &nid);
  1640   } else {
  1641     systray = FALSE;
  1642     Shell_NotifyIcon(NIM_DELETE, &nid);
  1643   }
  1644 }
  1645 #endif /* CYGWIN */
  1646 
  1647 void GuiServerLoop(struct CMDLINE *cmdline, gboolean is_service)
  1648 {
  1649   GtkWidget *window, *text, *hbox, *vbox, *entry, *label;
  1650   GIOChannel *listench;
  1651 
  1652   /* GTK+2 (and the GTK emulation code on WinNT systems) expects all
  1653    * strings to be UTF-8, so we force gettext to return all translations
  1654    * in this encoding here. */
  1655   bind_textdomain_codeset(PACKAGE, "UTF-8");
  1656 
  1657   Conv_SetInternalCodeset("UTF-8");
  1658   WantUTF8Errors(TRUE);
  1659 
  1660   if (cmdline) {
  1661     InitConfiguration(cmdline);
  1662   }
  1663 
  1664   window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  1665   g_signal_connect(G_OBJECT(window), "delete_event",
  1666                    G_CALLBACK(GuiRequestDelete), NULL);
  1667   gtk_window_set_default_size(GTK_WINDOW(window), 500, 250);
  1668 
  1669   /* Title of dopewars server window (if used) */
  1670   gtk_window_set_title(GTK_WINDOW(window), _("dopewars server"));
  1671 
  1672   gtk_container_set_border_width(GTK_CONTAINER(window), 7);
  1673 
  1674   vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 7);
  1675   TextOutput = text = gtk_scrolled_text_view_new(&hbox);
  1676   gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
  1677   gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
  1678   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
  1679 
  1680   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
  1681   label = gtk_label_new(_("Command:"));
  1682   gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
  1683   entry = gtk_entry_new();
  1684   g_signal_connect(G_OBJECT(entry), "activate",
  1685                    G_CALLBACK(GuiDoCommand), NULL);
  1686   gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
  1687   gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
  1688 
  1689   gtk_container_add(GTK_CONTAINER(window), vbox);
  1690   gtk_widget_show_all(window);
  1691 
  1692   if (is_service) {
  1693 #ifdef CYGWIN
  1694     g_log_set_handler(NULL, G_LOG_LEVEL_CRITICAL, ServiceFailure, NULL);
  1695 #endif
  1696   } else {
  1697     g_set_print_handler(GuiServerPrintFunc);
  1698     g_log_set_handler(NULL,
  1699                       LogMask() | G_LOG_LEVEL_MESSAGE |
  1700                       G_LOG_LEVEL_WARNING, GuiServerLogMessage, NULL);
  1701   }
  1702   if (!StartServer())
  1703     return;
  1704   InitMetaServer();
  1705 
  1706 #ifdef CYGIN
  1707   listench = g_io_channel_win32_new_socket(ListenSock);
  1708 #else
  1709   listench = g_io_channel_unix_new(ListenSock);
  1710 #endif
  1711   ListenTag = dp_g_io_add_watch(listench, G_IO_IN, GuiNewConnect, NULL);
  1712 #ifdef CYGWIN
  1713   mainhwnd = window->hWnd;
  1714   SetupTaskBarIcon(window);
  1715   SetCustomWndProc(GuiServerWndProc);
  1716   if (is_service && !RegisterStatus(SERVICE_RUNNING)) {
  1717     dopelog(0, LF_SERVER, _("Failed to set NT Service status"));
  1718     return;
  1719   }
  1720 #endif
  1721   gtk_main();
  1722 #ifdef CYGWIN
  1723   SetupTaskBarIcon(NULL);
  1724 #endif
  1725 }
  1726 #endif /* GUI_SERVER */
  1727 
  1728 #endif /* NETWORKING */
  1729 
  1730 /* 
  1731  * Tells player "Play" that the game is over; display "Message".
  1732  */
  1733 void FinishGame(Player *Play, char *Message)
  1734 {
  1735   Play->EventNum = E_FINISH;
  1736   ClientLeftServer(Play);
  1737   SendHighScores(Play, TRUE, Message);
  1738 
  1739   /* Blank the name, so that CountPlayers ignores this player */
  1740   SetPlayerName(Play, NULL);
  1741 
  1742   /* Make sure they do actually disconnect, eventually! */
  1743   if (ConnectTimeout) {
  1744     Play->ConnectTimeout = time(NULL) + (time_t) ConnectTimeout;
  1745   }
  1746 }
  1747 
  1748 /* 
  1749  * Reads a batch of NUMHISCORE high scores into "HiScore" from "fp".
  1750  */
  1751 void HighScoreTypeRead(struct HISCORE *HiScore, FILE *fp)
  1752 {
  1753   int i;
  1754   char *buf;
  1755 
  1756   for (i = 0; i < NUMHISCORE; i++) {
  1757     if (read_string(fp, &HiScore[i].Name) == EOF)
  1758       break;
  1759     read_string(fp, &HiScore[i].Time);
  1760     read_string(fp, &buf);
  1761     HiScore[i].Money = strtoprice(buf);
  1762     g_free(buf);
  1763     HiScore[i].Dead = (fgetc(fp) > 0);
  1764   }
  1765 }
  1766 
  1767 /* 
  1768  * Writes out a batch of NUMHISCORE high scores from "HiScore" to "fp".
  1769  */
  1770 void HighScoreTypeWrite(struct HISCORE *HiScore, FILE *fp)
  1771 {
  1772   int i;
  1773   gchar *text;
  1774 
  1775   for (i = 0; i < NUMHISCORE; i++) {
  1776     if (HiScore[i].Name) {
  1777       fwrite(HiScore[i].Name, strlen(HiScore[i].Name) + 1, 1, fp);
  1778     } else
  1779       fputc(0, fp);
  1780     if (HiScore[i].Time) {
  1781       fwrite(HiScore[i].Time, strlen(HiScore[i].Time) + 1, 1, fp);
  1782     } else
  1783       fputc(0, fp);
  1784     text = pricetostr(HiScore[i].Money);
  1785     fwrite(text, strlen(text) + 1, 1, fp);
  1786     g_free(text);
  1787     fputc(HiScore[i].Dead ? 1 : 0, fp);
  1788   }
  1789 }
  1790 
  1791 /* 
  1792  * Closes the high score file opened by OpenHighScoreFile, below.
  1793  */
  1794 void CloseHighScoreFile()
  1795 {
  1796   if (ScoreFP) {
  1797     fclose(ScoreFP);
  1798   }
  1799   ScoreFP = NULL;
  1800 }
  1801 
  1802 /* 
  1803  * If we're running setuid/setgid, drop down to the privilege level of the
  1804  * user that started the dopewars process.
  1805  */
  1806 void DropPrivileges()
  1807 {
  1808 #ifndef CYGWIN
  1809 
  1810 #ifdef HAVE_ISSETUGID
  1811   if (issetugid() == 0) return;
  1812 #endif
  1813 
  1814   /* Ignore the return from setregid; we'll check it ourselves to be sure
  1815    * (this avoids problems when running under fakeroot) */
  1816   setregid(getgid(), getgid());
  1817   if (getgid() != getegid()) {
  1818     perror("setregid");
  1819     exit(EXIT_FAILURE);
  1820   }
  1821 
  1822   setreuid(getuid(), getuid());
  1823   if (getuid() != geteuid()) {
  1824     perror("setreuid");
  1825     exit(EXIT_FAILURE);
  1826   }
  1827 #endif
  1828 }
  1829 
  1830 static const gchar SCOREHEADER[] = "DOPEWARS SCORES V.";
  1831 static const guint SCOREHDRLEN = sizeof(SCOREHEADER) - 1; /* Don't include \0 */
  1832 static const guint SCOREVERSION = 1;
  1833 
  1834 static gboolean HighScoreReadHeader(FILE *fp, gint *ScoreVersion)
  1835 {
  1836   gchar *header;
  1837 
  1838   if (read_string(fp, &header) != EOF) {
  1839     if (header && strlen(header) > SCOREHDRLEN &&
  1840         strncmp(header, SCOREHEADER, SCOREHDRLEN) == 0) {
  1841       if (ScoreVersion)
  1842         *ScoreVersion = atoi(header + SCOREHDRLEN);
  1843       g_free(header);
  1844       return TRUE;
  1845     }
  1846   }
  1847   g_free(header);
  1848   return FALSE;
  1849 }
  1850 
  1851 static void HighScoreWriteHeader(FILE *fp)
  1852 {
  1853   gchar *header;
  1854 
  1855   header = g_strdup_printf("%s%d", SCOREHEADER, SCOREVERSION);
  1856   fwrite(header, strlen(header) + 1, 1, fp);
  1857   g_free(header);
  1858 }
  1859 
  1860 /* 
  1861  * Converts an old format high score file to the new format.
  1862  */
  1863 void ConvertHighScoreFile(const gchar *convertfile)
  1864 {
  1865   FILE *old;
  1866   gchar *BackupFile;
  1867   int ch;
  1868   struct HISCORE MultiScore[NUMHISCORE], AntiqueScore[NUMHISCORE];
  1869 
  1870   BackupFile = g_strdup_printf("%s.bak", convertfile);
  1871 
  1872   old = fopen(convertfile, "r+");
  1873   if (old) {
  1874     gboolean empty;
  1875     /* Only convert if the file is not empty, and does not have a header */
  1876     rewind(old);
  1877     empty = (fgetc(old) == EOF);
  1878     rewind(old);
  1879     if (!empty && !HighScoreReadHeader(old, NULL)) {
  1880       FILE *backup = fopen(BackupFile, "w");
  1881       if (backup) {
  1882         /* Make a backup of the old file */
  1883         ftruncate(fileno(backup), 0);
  1884         rewind(backup);
  1885         rewind(old);
  1886         while (1) {
  1887           ch = fgetc(old);
  1888           if (ch == EOF) {
  1889             break;
  1890           } else {
  1891             fputc(ch, backup);
  1892           }
  1893         }
  1894         fclose(backup);
  1895 
  1896         /* Read in the scores without the header, and then write out with
  1897          * the header */
  1898         if (!HighScoreRead(old, MultiScore, AntiqueScore, FALSE)) {
  1899           g_log(NULL, G_LOG_LEVEL_CRITICAL,
  1900                 _("Error reading scores from %s."), convertfile);
  1901         } else {
  1902           ftruncate(fileno(old), 0);
  1903           rewind(old);
  1904           if (HighScoreWrite(old, MultiScore, AntiqueScore)) {
  1905             g_message(_("The high score file %s has been converted to the "
  1906                         "new format.\nA backup of the old file has been "
  1907                         "created as %s.\n"), convertfile, BackupFile);
  1908           }
  1909         }
  1910       } else {
  1911         gchar *errmsg = ErrStrFromErrno(errno);
  1912         g_log(NULL, G_LOG_LEVEL_CRITICAL,
  1913               _("Cannot create backup (%s) of the\nhigh score file: %s."),
  1914               BackupFile, errmsg);
  1915         g_free(errmsg);
  1916       }
  1917     }
  1918     fclose(old);
  1919   } else {
  1920     gchar *errmsg = ErrStrFromErrno(errno);
  1921     g_log(NULL, G_LOG_LEVEL_CRITICAL,
  1922           _("Cannot open high score file %s: %s."),
  1923           convertfile, errmsg);
  1924     g_free(errmsg);
  1925   }
  1926 
  1927   g_free(BackupFile);
  1928 }
  1929 
  1930 #ifdef CYGWIN
  1931 /* Try to open a high score file in the Win32-specific AppData directory */
  1932 static FILE *OpenHighScoreAppData(int *error, gboolean *empty)
  1933 {
  1934   FILE *fp = NULL;
  1935   HKEY key;
  1936   static const char *subkey = "Software\\Microsoft\\Windows\\CurrentVersion"
  1937                               "\\Explorer\\Shell Folders";
  1938   static const char *subval = "Local AppData";
  1939 
  1940   *empty = FALSE;
  1941 
  1942   if (RegOpenKeyEx(HKEY_CURRENT_USER, subkey, 0, KEY_READ,
  1943                    &key) == ERROR_SUCCESS) {
  1944     DWORD keylen, keytype;
  1945     if (RegQueryValueEx(key, subval, NULL, &keytype, NULL,
  1946                         &keylen) == ERROR_SUCCESS && keytype == REG_SZ) {
  1947       char *keyval = g_malloc(keylen);
  1948       if (RegQueryValueEx(key, subval, NULL, &keytype, (LPBYTE)keyval,
  1949                           &keylen) == ERROR_SUCCESS) {
  1950         GString *str = g_string_sized_new(keylen + 40);
  1951         g_string_assign(str, keyval);
  1952         g_free(keyval);
  1953         g_string_append(str, "\\dopewars");
  1954         CreateDirectory(str->str, NULL);
  1955         g_string_append(str, "\\dopewars.sco");
  1956         fp = fopen(str->str, "r+");
  1957         if (!fp) {
  1958           fp = fopen(str->str, "w+");
  1959           if (!fp) {
  1960             *error = errno;
  1961           }
  1962           *empty = TRUE;
  1963         }
  1964         g_string_free(str, 1);
  1965       }
  1966     }
  1967     RegCloseKey(key);
  1968   }
  1969   return fp;
  1970 }
  1971 #endif
  1972 
  1973 
  1974 /* State, set by OpenHighScoreFile, and later used by
  1975  * CheckHighScoreFileConfig */
  1976 static gboolean EmptyFile;
  1977 static int OpenError;
  1978 
  1979 /* 
  1980  * Opens the high score file for later use, and then drops privileges.
  1981  */
  1982 void OpenHighScoreFile(void)
  1983 {
  1984   if (ScoreFP) {
  1985     return;                     /* If already opened, then we're done */
  1986   }
  1987 
  1988   EmptyFile = FALSE;
  1989   OpenError = 0;
  1990 
  1991   /* Win32 gets upset if we use "a+" so we use this nasty hack instead */
  1992   ScoreFP = fopen(HiScoreFile, "r+");
  1993   if (!ScoreFP) {
  1994     ScoreFP = fopen(HiScoreFile, "w+");
  1995     if (!ScoreFP) {
  1996       OpenError = errno;
  1997     }
  1998     EmptyFile = TRUE;
  1999   }
  2000 #ifdef CYGWIN
  2001   if (!ScoreFP) {
  2002     ScoreFP = OpenHighScoreAppData(&OpenError, &EmptyFile);
  2003   }
  2004 #endif
  2005 
  2006   /* Check for a 0-byte score file */
  2007   if (ScoreFP && !EmptyFile) {
  2008     rewind(ScoreFP);
  2009     if (fgetc(ScoreFP) == EOF) {
  2010       EmptyFile = TRUE;
  2011     }
  2012     rewind(ScoreFP);
  2013   }
  2014 }
  2015 
  2016 /* 
  2017  * Checks the high score file opened by OpenHighScoreFile, above. Also warns
  2018  * the user about other problems encountered during startup. Returns
  2019  * TRUE if it's valid; otherwise, returns FALSE.
  2020  */
  2021 gboolean CheckHighScoreFileConfig(void)
  2022 {
  2023 
  2024   if (!ScoreFP) {
  2025     gchar *errstr = ErrStrFromErrno(OpenError);
  2026     g_log(NULL, G_LOG_LEVEL_CRITICAL,
  2027           _("Cannot open high score file %s.\n"
  2028             "(%s.) Either ensure you have permissions to access\n"
  2029             "this file and directory, or specify an alternate high score "
  2030             "file with the\n-f command line option."),
  2031           HiScoreFile, errstr);
  2032     g_free(errstr);
  2033     return FALSE;
  2034   }
  2035 
  2036   if (EmptyFile) {
  2037     HighScoreWriteHeader(ScoreFP);
  2038     fflush(ScoreFP);
  2039   } else if (!HighScoreReadHeader(ScoreFP, NULL)) {
  2040     g_log(NULL, G_LOG_LEVEL_CRITICAL,
  2041           _("%s does not appear to be a valid\n"
  2042             "high score file - please check it. If it is a high score file\n"
  2043             "from an older version of dopewars, then first convert it to the\n"
  2044             "new format by running \"dopewars -C %s\"\n"
  2045             "from the command line."), HiScoreFile, HiScoreFile);
  2046     return FALSE;
  2047   }
  2048 
  2049   if (ConfigErrors) {
  2050 #ifdef CYGWIN
  2051     g_warning(_("Errors were encountered during the reading of the "
  2052                 "configuration file.\nAs as result, some settings may not "
  2053                 "work as expected. Please consult the\n"
  2054                 "file \"dopewars-log.txt\" for further details."));
  2055 #else
  2056     g_warning(_("Errors were encountered during the reading of the "
  2057                 "configuration\nfile. As a result, some settings may not "
  2058                 "work as expected. Please see the\nmessages on standard "
  2059                 "output for further details."));
  2060 #endif
  2061   }
  2062 
  2063   return TRUE;
  2064 }
  2065 
  2066 /* 
  2067  * Reads all the high scores into MultiScore and AntiqueScore (antique
  2068  * mode scores). If ReadHeader is TRUE, read the high score file header
  2069  * first. Returns TRUE on success, FALSE on failure.
  2070  */
  2071 gboolean HighScoreRead(FILE *fp, struct HISCORE *MultiScore,
  2072                        struct HISCORE *AntiqueScore, gboolean ReadHeader)
  2073 {
  2074   gint ScoreVersion = 0;
  2075   memset(MultiScore, 0, sizeof(struct HISCORE) * NUMHISCORE);
  2076   memset(AntiqueScore, 0, sizeof(struct HISCORE) * NUMHISCORE);
  2077   if (fp && ReadLock(fp) == 0) {
  2078     rewind(fp);
  2079     if (ReadHeader && !HighScoreReadHeader(fp, &ScoreVersion)) {
  2080       ReleaseLock(fp);
  2081       return FALSE;
  2082     }
  2083     HighScoreTypeRead(AntiqueScore, fp);
  2084     HighScoreTypeRead(MultiScore, fp);
  2085     ReleaseLock(fp);
  2086   } else
  2087     return FALSE;
  2088   return TRUE;
  2089 }
  2090 
  2091 /* 
  2092  * Writes out all the high scores from MultiScore and AntiqueScore; returns
  2093  * TRUE on success, FALSE on failure.
  2094  */
  2095 gboolean HighScoreWrite(FILE *fp, struct HISCORE *MultiScore,
  2096                         struct HISCORE *AntiqueScore)
  2097 {
  2098   if (fp && WriteLock(fp) == 0) {
  2099     ftruncate(fileno(fp), 0);
  2100     rewind(fp);
  2101     HighScoreWriteHeader(fp);
  2102     HighScoreTypeWrite(AntiqueScore, fp);
  2103     HighScoreTypeWrite(MultiScore, fp);
  2104     ReleaseLock(fp);
  2105     fflush(fp);
  2106   } else
  2107     return 0;
  2108   return 1;
  2109 }
  2110 
  2111 /* 
  2112  * Adds "Play" to the high score list if necessary, and then sends the
  2113  * scores over the network to "Play".
  2114  * If "EndGame" is TRUE, add the current score if it's high enough and
  2115  * display an explanatory message. "Message" is tacked onto the start
  2116  * if it's non-NULL. The client is then informed that the game's over.
  2117  */
  2118 void SendHighScores(Player *Play, gboolean EndGame, char *Message)
  2119 {
  2120   struct HISCORE MultiScore[NUMHISCORE], AntiqueScore[NUMHISCORE], Score;
  2121   struct HISCORE *HiScore;
  2122   struct tm *timep;
  2123   time_t tim;
  2124   GString *text;
  2125   int i, j, InList = -1;
  2126 
  2127   text = g_string_new("");
  2128   if (!HighScoreRead(ScoreFP, MultiScore, AntiqueScore, TRUE)) {
  2129     g_warning(_("Unable to read high score file %s"), HiScoreFile);
  2130   }
  2131   if (Message) {
  2132     g_string_assign(text, Message);
  2133     if (strlen(text->str) > 0)
  2134       g_string_append_c(text, '^');
  2135   }
  2136   if (WantAntique)
  2137     HiScore = AntiqueScore;
  2138   else
  2139     HiScore = MultiScore;
  2140   if (EndGame) {
  2141     Score.Money = Play->Cash + Play->Bank - Play->Debt;
  2142     Score.Name = g_strdup(GetPlayerName(Play));
  2143     Score.Dead = (Play->Health == 0);
  2144     tim = time(NULL);
  2145     timep = gmtime(&tim);
  2146     Score.Time = g_new(char, 80);       /* Yuck! */
  2147 
  2148     strftime(Score.Time, 80, "%d-%m-%Y", timep);
  2149     Score.Time[79] = '\0';
  2150     for (i = 0; i < NUMHISCORE; i++) {
  2151       if (InList == -1 && (Score.Money > HiScore[i].Money ||
  2152                            !HiScore[i].Time || HiScore[i].Time[0] == 0)) {
  2153         InList = i;
  2154         g_string_append(text,
  2155                         _("Congratulations! You made the high scores!"));
  2156         SendPrintMessage(NULL, C_NONE, Play, text->str);
  2157         g_free(HiScore[NUMHISCORE - 1].Name);
  2158         g_free(HiScore[NUMHISCORE - 1].Time);
  2159         for (j = NUMHISCORE - 1; j > i; j--) {
  2160           memcpy(&HiScore[j], &HiScore[j - 1], sizeof(struct HISCORE));
  2161         }
  2162         memcpy(&HiScore[i], &Score, sizeof(struct HISCORE));
  2163         break;
  2164       }
  2165     }
  2166     if (InList == -1) {
  2167       g_string_append(text,
  2168                       _("You didn't even make the high score table..."));
  2169       SendPrintMessage(NULL, C_NONE, Play, text->str);
  2170     }
  2171   }
  2172   SendServerMessage(NULL, C_NONE, C_STARTHISCORE, Play, NULL);
  2173 
  2174   j = 0;
  2175   for (i = 0; i < NUMHISCORE; i++) {
  2176     if (SendSingleHighScore(Play, &HiScore[i], j, InList == i))
  2177       j++;
  2178   }
  2179   if (InList == -1 && EndGame) {
  2180     SendSingleHighScore(Play, &Score, j, TRUE);
  2181     g_free(Score.Name);
  2182     g_free(Score.Time);
  2183   }
  2184   SendServerMessage(NULL, C_NONE, C_ENDHISCORE, Play,
  2185                     EndGame ? "end" : NULL);
  2186   if (!EndGame)
  2187     SendDrugsHere(Play, FALSE);
  2188   if (EndGame && !HighScoreWrite(ScoreFP, MultiScore, AntiqueScore)) {
  2189     g_warning(_("Unable to write high score file %s"), HiScoreFile);
  2190   }
  2191   for (i = 0; i < NUMHISCORE; i++) {
  2192     g_free(MultiScore[i].Name);
  2193     g_free(MultiScore[i].Time);
  2194     g_free(AntiqueScore[i].Name);
  2195     g_free(AntiqueScore[i].Time);
  2196   }
  2197   g_string_free(text, TRUE);
  2198 }
  2199 
  2200 /* 
  2201  * Sends a single high score in "Score" with position "ind" to player
  2202  * "Play". If Bold is TRUE, instructs the client to display the score in
  2203  * bold text.
  2204  */
  2205 int SendSingleHighScore(Player *Play, struct HISCORE *Score,
  2206                         int ind, gboolean Bold)
  2207 {
  2208   gchar *Data, *prstr;
  2209 
  2210   if (!Score->Time || Score->Time[0] == 0)
  2211     return 0;
  2212   Data = g_strdup_printf("%d^%c%c%18s  %-14s %-34s %8s%c", ind,
  2213                          Bold ? 'B' : 'N', Bold ? '>' : ' ',
  2214                          prstr = FormatPrice(Score->Money),
  2215                          Score->Time, Score->Name,
  2216                          Score->Dead ? _("(R.I.P.)") : "",
  2217                          Bold ? '<' : ' ');
  2218   SendServerMessage(NULL, C_NONE, C_HISCORE, Play, Data);
  2219   g_free(prstr);
  2220   g_free(Data);
  2221   return 1;
  2222 }
  2223 
  2224 /* 
  2225  * In order for the server to keep track of the state of each client, each
  2226  * client's state is identified by its EventNum data member. So, for example,
  2227  * there is a state for fighting the cops, a state for going to the bank, and
  2228  * so on. This function instructs client player "To" to carry out the actions
  2229  * expected of it in its current state. It is the client's responsibility to
  2230  * ensure that it carries out the correct actions to advance itself to the
  2231  * "next" state; if it fails in this duty it will hang!
  2232  */
  2233 void SendEvent(Player *To)
  2234 {
  2235   price_t Money;
  2236   int i, j;
  2237   gchar *text;
  2238   Player *Play;
  2239   GSList *list;
  2240 
  2241   if (!To)
  2242     return;
  2243   if (To->EventNum == E_MAX)
  2244     To->EventNum = E_NONE;
  2245   if (To->EventNum == E_NONE || To->EventNum >= E_OUTOFSYNC)
  2246     return;
  2247   Money = To->Cash + To->Bank - To->Debt;
  2248 
  2249   ClearPrices(To);
  2250 
  2251   while (To->EventNum < E_MAX) {
  2252     switch (To->EventNum) {
  2253     case E_SUBWAY:
  2254       SendServerMessage(NULL, C_NONE, C_SUBWAYFLASH, To, NULL);
  2255       break;
  2256     case E_OFFOBJECT:
  2257       To->OnBehalfOf = NULL;
  2258       for (i = 0; i < To->TipList.Number; i++) {
  2259         dopelog(3, LF_SERVER, _("%s: Tipoff from %s"), GetPlayerName(To),
  2260                 GetPlayerName(To->TipList.Data[i].Play));
  2261         To->OnBehalfOf = To->TipList.Data[i].Play;
  2262         SendCopOffer(To, FORCECOPS);
  2263         return;
  2264       }
  2265       for (i = 0; i < To->SpyList.Number; i++) {
  2266         if (To->SpyList.Data[i].Turns < 0) {
  2267           dopelog(3, LF_SERVER, _("%s: Spy offered by %s"), GetPlayerName(To),
  2268                   GetPlayerName(To->SpyList.Data[i].Play));
  2269           To->OnBehalfOf = To->SpyList.Data[i].Play;
  2270           SendCopOffer(To, FORCEBITCH);
  2271           return;
  2272         }
  2273         To->SpyList.Data[i].Turns++;
  2274         if (To->SpyList.Data[i].Turns > 3 &&
  2275             brandom(0, 100) < 10 + To->SpyList.Data[i].Turns) {
  2276           if (TotalGunsCarried(To) > 0)
  2277             j = brandom(0, NUMDISCOVER);
  2278           else
  2279             j = brandom(0, NUMDISCOVER - 1);
  2280           text =
  2281               dpg_strdup_printf(_("One of your %tde was spying for %s."
  2282                                   "^The spy %s!"), Names.Bitches,
  2283                                 GetPlayerName(To->SpyList.Data[i].Play),
  2284                                 _(Discover[j]));
  2285           if (j != DEFECT)
  2286             LoseBitch(To, NULL, NULL);
  2287           SendPlayerData(To);
  2288           SendPrintMessage(NULL, C_NONE, To, text);
  2289           g_free(text);
  2290           text = g_strdup_printf(_("Your spy working with %s has "
  2291                                    "been discovered!^The spy %s!"),
  2292                                  GetPlayerName(To), _(Discover[j]));
  2293           if (j == ESCAPE)
  2294             GainBitch(To->SpyList.Data[i].Play);
  2295           To->SpyList.Data[i].Play->Flags &= ~SPYINGON;
  2296           SendPlayerData(To->SpyList.Data[i].Play);
  2297           SendPrintMessage(NULL, C_NONE, To->SpyList.Data[i].Play, text);
  2298           g_free(text);
  2299           RemoveListEntry(&(To->SpyList), i);
  2300           i--;
  2301         }
  2302       }
  2303       if (Money > 3000000)
  2304         i = 130;
  2305       else if (Money > 1000000)
  2306         i = 115;
  2307       else
  2308         i = 100;
  2309       if (brandom(0, i) > 75) {
  2310         if (SendCopOffer(To, NOFORCE))
  2311           return;
  2312       }
  2313       break;
  2314     case E_SAYING:
  2315       if (!Sanitized && brandom(0, 100) < 15
  2316           && (NumSubway > 0 || NumPlaying > 0)) {
  2317         int subwaychance = 50;
  2318 
  2319         if (NumSubway == 0)
  2320           subwaychance = 0;
  2321         if (NumPlaying == 0)
  2322           subwaychance = 100;
  2323         if (brandom(0, 100) < subwaychance) {
  2324           text = g_strdup_printf(_("The lady next to you on the subway "
  2325                                    "said,^ \"%s\"%s"),
  2326                                  SubwaySaying[brandom(0, NumSubway)],
  2327                                  brandom(0, 100) < 30 ?
  2328                                  _("^    (at least, you -think- that's "
  2329                                    "what she said)") : "");
  2330         } else {
  2331           text = g_strdup_printf(_("You hear someone playing %s"),
  2332                                  Playing[brandom(0, NumPlaying)]);
  2333         }
  2334         SendPrintMessage(NULL, C_NONE, To, text);
  2335         g_free(text);
  2336       }
  2337       break;
  2338     case E_LOANSHARK:
  2339       if (To->IsAt + 1 == LoanSharkLoc && To->Debt > 0) {
  2340         text = dpg_strdup_printf(_("YN^Would you like to visit %tde?"),
  2341                                  Names.LoanSharkName);
  2342         SendQuestion(NULL, C_ASKLOAN, To, text);
  2343         g_free(text);
  2344         return;
  2345       }
  2346       break;
  2347     case E_BANK:
  2348       if (To->IsAt + 1 == BankLoc) {
  2349         text = dpg_strdup_printf(_("YN^Would you like to visit %tde?"),
  2350                                  Names.BankName);
  2351         SendQuestion(NULL, C_ASKBANK, To, text);
  2352         g_free(text);
  2353         return;
  2354       }
  2355       break;
  2356     case E_GUNSHOP:
  2357       if (To->IsAt + 1 == GunShopLoc && !Sanitized && NumGun > 0) {
  2358         text = dpg_strdup_printf(_("YN^Would you like to visit %tde?"),
  2359                                  Names.GunShopName);
  2360         SendQuestion(NULL, C_ASKGUNSHOP, To, text);
  2361         g_free(text);
  2362         return;
  2363       }
  2364       break;
  2365     case E_ROUGHPUB:
  2366       if (To->IsAt + 1 == RoughPubLoc) {
  2367         text = dpg_strdup_printf(_("YN^Would you like to visit %tde?"),
  2368                                  Names.RoughPubName);
  2369         SendQuestion(NULL, C_ASKPUB, To, text);
  2370         g_free(text);
  2371         return;
  2372       }
  2373       break;
  2374     case E_HIREBITCH:
  2375       if (To->IsAt + 1 == RoughPubLoc) {
  2376         To->Bitches.Price = prandom(Bitch.MinPrice, Bitch.MaxPrice);
  2377         text =
  2378             dpg_strdup_printf(_
  2379                               ("YN^^Would you like to hire a %tde for %P?"),
  2380                               Names.Bitch, To->Bitches.Price);
  2381         SendQuestion(NULL, C_ASKBITCH, To, text);
  2382         g_free(text);
  2383         return;
  2384       }
  2385       break;
  2386     case E_ARRIVE:
  2387       for (list = FirstServer; list; list = g_slist_next(list)) {
  2388         Play = (Player *)list->data;
  2389         if (IsConnectedPlayer(Play) && Play != To
  2390             && NumGun > 0 && Play->IsAt == To->IsAt
  2391             && Play->EventNum == E_NONE && TotalGunsCarried(To) > 0) {
  2392           text = g_strdup_printf(_("%s^%s is already here!^"
  2393                                    "Do you Attack, or Evade?"),
  2394                                  attackquestiontr,
  2395                                  GetPlayerName(Play));
  2396           /* Steal this to keep track of the potential defender */
  2397           To->OnBehalfOf = Play;
  2398 
  2399           SendDrugsHere(To, TRUE);
  2400           SendQuestion(NULL, C_MEETPLAYER, To, text);
  2401           g_free(text);
  2402           return;
  2403         }
  2404       }
  2405       SendDrugsHere(To, TRUE);
  2406       break;
  2407     default:
  2408       break;
  2409     }
  2410     To->EventNum++;
  2411   }
  2412   if (To->EventNum >= E_MAX)
  2413     To->EventNum = E_NONE;
  2414 }
  2415 
  2416 /* 
  2417  * In response to client player "To" being in state E_OFFOBJECT,
  2418  * randomly engages the client in combat with the cops or offers
  2419  * other random events. Returns 0 if the client should then be
  2420  * advanced to the next state, 1 otherwise (i.e. if there are
  2421  * questions pending which the client must answer first)
  2422  * If Force==FORCECOPS, engage in combat with the cops for certain
  2423  * If Force==FORCEBITCH, offer the client a bitch for certain
  2424  */
  2425 int SendCopOffer(Player *To, OfferForce Force)
  2426 {
  2427   int i;
  2428 
  2429   /* The cops are more likely to attack in locations with higher police
  2430    * presence ratings */
  2431   i = brandom(0, 80 + Location[To->IsAt].PolicePresence);
  2432 
  2433   if (Force == FORCECOPS)
  2434     i = 100;
  2435   else if (Force == FORCEBITCH)
  2436     i = 0;
  2437   else
  2438     To->OnBehalfOf = NULL;
  2439   if (i < 33) {
  2440     return (OfferObject(To, Force == FORCEBITCH));
  2441   } else if (i < 50) {
  2442     return (RandomOffer(To));
  2443   } else if (Sanitized || NumCop == 0 || NumGun == 0) {
  2444     return 0;
  2445   } else {
  2446     CopsAttackPlayer(To);
  2447     return 1;
  2448   }
  2449   return 1;
  2450 }
  2451 
  2452 /* 
  2453  * Has the cops attack player "Play".
  2454  */
  2455 void CopsAttackPlayer(Player *Play)
  2456 {
  2457   Player *Cops;
  2458   gint CopIndex, NumDeputy, GunIndex;
  2459 
  2460   if (NumCop == 0 || NumGun == 0) {
  2461     g_warning(_("No cops or guns!"));
  2462     return;
  2463   }
  2464 
  2465   CopIndex = 1 - Play->CopIndex;
  2466   if (CopIndex < 0) {
  2467     g_warning(_("Cops cannot attack other cops!"));
  2468     return;
  2469   }
  2470   if (CopIndex > NumCop)
  2471     CopIndex = NumCop;
  2472   Cops = g_new(Player, 1);
  2473 
  2474   FirstServer = AddPlayer(0, Cops, FirstServer);
  2475   SetPlayerName(Cops, Cop[CopIndex - 1].Name);
  2476   Cops->CopIndex = CopIndex;
  2477   Cops->Cash = brandom(100, 2000);
  2478   Cops->Debt = Cops->Bank = 0;
  2479 
  2480   NumDeputy = brandom(Cop[CopIndex - 1].MinDeputies,
  2481                       Cop[CopIndex - 1].MaxDeputies);
  2482   Cops->Bitches.Carried = NumDeputy;
  2483   GunIndex = Cop[CopIndex - 1].GunIndex;
  2484   if (GunIndex >= NumGun)
  2485     GunIndex = NumGun - 1;
  2486   Cops->Guns[GunIndex].Carried =
  2487       (NumDeputy * Cop[CopIndex - 1].DeputyGun) + Cop[CopIndex - 1].CopGun;
  2488   Cops->Health = 100;
  2489 
  2490   Play->EventNum++;
  2491   AttackPlayer(Cops, Play);
  2492 }
  2493 
  2494 /* 
  2495  * Starts combat between player "Play" and player "Attacked"; if
  2496  * either player is currently engaged in combat, add the other
  2497  * player to the existing combat. If neither player is currently
  2498  * fighting, start a new combat between them. Either player can be
  2499  * the cops.
  2500  */
  2501 void AttackPlayer(Player *Play, Player *Attacked)
  2502 {
  2503   GPtrArray *FightArray;
  2504 
  2505   g_assert(Play && Attacked);
  2506 
  2507   if (Play->FightArray && Attacked->FightArray) {
  2508     if (Play->FightArray == Attacked->FightArray) {
  2509       g_error(_("Players are already in a fight!"));
  2510     } else {
  2511       g_error(_("Players are already in separate fights!"));
  2512     }
  2513     return;
  2514   }
  2515   if (NumGun == 0) {
  2516     g_error(_("Cannot start fight - no guns to use!"));
  2517     return;
  2518   }
  2519 
  2520   if (!Play->FightArray && !Attacked->FightArray) {
  2521     FightArray = g_ptr_array_new();
  2522   } else {
  2523     FightArray =
  2524         Play->FightArray ? Play->FightArray : Attacked->FightArray;
  2525   }
  2526 
  2527   if (!Play->FightArray) {
  2528     Play->ResyncNum = Play->EventNum;
  2529     g_ptr_array_add(FightArray, Play);
  2530   }
  2531   if (!Attacked->FightArray) {
  2532     Attacked->ResyncNum = Attacked->EventNum;
  2533     g_ptr_array_add(FightArray, Attacked);
  2534   }
  2535   Play->FightArray = Attacked->FightArray = FightArray;
  2536   Play->EventNum = Attacked->EventNum = E_FIGHT;
  2537 
  2538   Play->Attacking = Attacked;
  2539 
  2540   SendFightMessage(Attacked, Play, 0, F_ARRIVED, (price_t)0, TRUE, NULL);
  2541 
  2542   Fire(Play);
  2543 }
  2544 
  2545 /* 
  2546  * Returns TRUE if player "Other" is not allied with player "Play".
  2547  */
  2548 gboolean IsOpponent(Player *Play, Player *Other)
  2549 {
  2550   return TRUE;
  2551 }
  2552 
  2553 void HandleDamage(Player *Defend, Player *Attack, int Damage,
  2554                   int *BitchesKilled, price_t *Loot)
  2555 {
  2556   Inventory *Guns, *Drugs;
  2557   price_t Bounty;
  2558 
  2559   Guns = (Inventory *)g_malloc0(sizeof(Inventory) * NumGun);
  2560   Drugs = (Inventory *)g_malloc0(sizeof(Inventory) * NumDrug);
  2561   ClearInventory(Guns, Drugs);
  2562 
  2563   Bounty = 0;
  2564   if (Defend->Health <= Damage && Defend->Bitches.Carried == 0) {
  2565     Bounty = Defend->Cash + Defend->Bank - Defend->Debt;
  2566     AddInventory(Guns, Defend->Guns, NumGun);
  2567     AddInventory(Drugs, Defend->Drugs, NumDrug);
  2568     Defend->Health = 0;
  2569   } else if (Defend->Bitches.Carried > 0 && Defend->Health <= Damage) {
  2570     if (IsCop(Defend))
  2571       LoseBitch(Defend, NULL, NULL);
  2572     else
  2573       LoseBitch(Defend, Guns, Drugs);
  2574     Defend->Health = 100;
  2575     *BitchesKilled = 1;
  2576   } else {
  2577     Defend->Health -= Damage;
  2578   }
  2579   if (IsCop(Attack)) {          /* Don't let cops loot players */
  2580     ClearInventory(Guns, Drugs);
  2581   } else {
  2582     TruncateInventoryFor(Guns, Drugs, Attack);
  2583   }
  2584   SendPlayerData(Defend);
  2585   if (Bounty < 0)
  2586     Bounty = 0;
  2587   if (!IsInventoryClear(Guns, Drugs)) {
  2588     AddInventory(Attack->Guns, Guns, NumGun);
  2589     AddInventory(Attack->Drugs, Drugs, NumDrug);
  2590     ChangeSpaceForInventory(Guns, Drugs, Attack);
  2591   }
  2592   Attack->Cash += Bounty;
  2593   if (Bounty > 0 || !IsInventoryClear(Guns, Drugs)) {
  2594     if (Bounty > 0)
  2595       *Loot = Bounty;
  2596     else
  2597       *Loot = -1;
  2598     SendPlayerData(Attack);
  2599   }
  2600   g_free(Guns);
  2601   g_free(Drugs);
  2602 }
  2603 
  2604 void GetFightRatings(Player *Attack, Player *Defend,
  2605                      int *AttackRating, int *DefendRating)
  2606 {
  2607   int i;
  2608 
  2609   /* Base values */
  2610   *AttackRating = 80;
  2611   *DefendRating = 100;
  2612 
  2613   for (i = 0; i < NumGun; i++) {
  2614     *AttackRating += Gun[i].Damage * Attack->Guns[i].Carried;
  2615   }
  2616   if (IsCop(Attack))
  2617     *AttackRating -= Cop[Attack->CopIndex - 1].AttackPenalty;
  2618 
  2619   *DefendRating -= 5 * Defend->Bitches.Carried;
  2620   if (IsCop(Defend))
  2621     *DefendRating -= Cop[Defend->CopIndex - 1].DefendPenalty;
  2622 
  2623   *DefendRating = MAX(*DefendRating, 10);
  2624   *AttackRating = MAX(*AttackRating, 10);
  2625 }
  2626 
  2627 void AllowNextShooter(Player *Play)
  2628 {
  2629   Player *NextShooter;
  2630 
  2631   if (FightTimeout) {
  2632     NextShooter = GetNextShooter(Play);
  2633     if (NextShooter && !CanPlayerFire(NextShooter)) {
  2634       NextShooter->FightTimeout = 0;
  2635     }
  2636   }
  2637 }
  2638 
  2639 void DoReturnFire(Player *Play)
  2640 {
  2641   guint ArrayInd;
  2642   Player *Defend;
  2643 
  2644   if (!Play || !Play->FightArray)
  2645     return;
  2646 
  2647   if (FightTimeout != 0 || !IsCop(Play)) {
  2648     for (ArrayInd = 0;
  2649          Play->FightArray && ArrayInd < Play->FightArray->len;
  2650          ArrayInd++) {
  2651       Defend = (Player *)g_ptr_array_index(Play->FightArray, ArrayInd);
  2652       if (IsCop(Defend) && CanPlayerFire(Defend))
  2653         Fire(Defend);
  2654     }
  2655   }
  2656 }
  2657 
  2658 /*
  2659  * Puts the given player into the "fight ended" state.
  2660  */
  2661 static void WaitForFightDone(Player *Play)
  2662 {
  2663   if (HaveAbility(Play, A_DONEFIGHT)) {
  2664     Play->EventNum = E_WAITDONE;
  2665   } else {
  2666     Play->EventNum = Play->ResyncNum;
  2667     SendEvent(Play);
  2668   }
  2669 }
  2670 
  2671 /* 
  2672  * Withdraws player "Play" from combat, and levies any penalties on
  2673  * the player for this cowardly act, if applicable. If "ToLocation"
  2674  * is >=0, then it identifies the location that the player is
  2675  * trying to run to.
  2676  */
  2677 void RunFromCombat(Player *Play, int ToLocation)
  2678 {
  2679   int EscapeProb, RandNum;
  2680   guint ArrayInd;
  2681   gboolean FightingCop = FALSE;
  2682   Player *Defend;
  2683   char BackupAt;
  2684 
  2685   if (!Play || !Play->FightArray)
  2686     return;
  2687 
  2688   EscapeProb = 60;
  2689 
  2690   /* Penalise players that are attacking others */
  2691   if (Play->Attacking)
  2692     EscapeProb /= 2;
  2693 
  2694   RandNum = brandom(0, 100);
  2695 
  2696   if (RandNum < EscapeProb) {
  2697     if (!IsCop(Play) && brandom(0, 100) < 30) {
  2698       for (ArrayInd = 0; ArrayInd < Play->FightArray->len; ArrayInd++) {
  2699         Defend = (Player *)g_ptr_array_index(Play->FightArray, ArrayInd);
  2700         if (IsCop(Defend)) {
  2701           FightingCop = TRUE;
  2702           break;
  2703         }
  2704       }
  2705       if (FightingCop)
  2706         Play->CopIndex--;
  2707     }
  2708     BackupAt = Play->IsAt;
  2709     Play->IsAt = ToLocation;
  2710     WithdrawFromCombat(Play);
  2711     Play->IsAt = BackupAt;
  2712     WaitForFightDone(Play);
  2713   } else {
  2714     SendFightMessage(Play, NULL, 0, F_FAILFLEE, (price_t)0, TRUE, NULL);
  2715     AllowNextShooter(Play);
  2716     if (FightTimeout)
  2717       SetFightTimeout(Play);
  2718     DoReturnFire(Play);
  2719   }
  2720 }
  2721 
  2722 void CheckForKilledPlayers(Player *Play)
  2723 {
  2724   Player *Defend;
  2725   guint ArrayInd;
  2726   GPtrArray *KilledPlayers;
  2727 
  2728   KilledPlayers = g_ptr_array_new();
  2729   for (ArrayInd = 0; ArrayInd < Play->FightArray->len; ArrayInd++) {
  2730     Defend = (Player *)g_ptr_array_index(Play->FightArray, ArrayInd);
  2731 
  2732     if (Defend && Defend != Play && IsOpponent(Play, Defend) &&
  2733         Defend->Health == 0) {
  2734       g_ptr_array_add(KilledPlayers, (gpointer)Defend);
  2735     }
  2736   }
  2737   for (ArrayInd = 0; ArrayInd < KilledPlayers->len; ArrayInd++) {
  2738     Defend = (Player *)g_ptr_array_index(KilledPlayers, ArrayInd);
  2739     WithdrawFromCombat(Defend);
  2740     if (IsCop(Defend)) {
  2741       if (!IsCop(Play))
  2742         Play->CopIndex = -Defend->CopIndex;
  2743       FirstServer = RemovePlayer(Defend, FirstServer);
  2744     } else {
  2745       FinishGame(Defend, _("You're dead! Game over."));
  2746     }
  2747   }
  2748 
  2749   g_ptr_array_free(KilledPlayers, TRUE);
  2750 }
  2751 
  2752 /* 
  2753  * If "Play" is attacking someone, and no cops are currently present,
  2754  * then have the cops intervene (with a probability dependent on the
  2755  * current location's PolicePresence)
  2756  */
  2757 static void CheckCopsIntervene(Player *Play)
  2758 {
  2759   guint ArrayInd;
  2760   Player *Defend;
  2761 
  2762   if (!Play || !Play->FightArray || NumCop == 0 || NumGun == 0)
  2763     return;                     /* Sanity check */
  2764 
  2765   if (!Play->Attacking)
  2766     return;                     /* Cops don't attack "innocent victims" ;) */
  2767 
  2768   if (brandom(0, 100) > Location[Play->IsAt].PolicePresence) {
  2769     return;                     /* The cops shouldn't _always_ attack
  2770                                  * (unless P.P. == 100) */
  2771   }
  2772 
  2773   for (ArrayInd = 0; Play->FightArray && ArrayInd < Play->FightArray->len;
  2774        ArrayInd++) {
  2775     Defend = (Player *)g_ptr_array_index(Play->FightArray, ArrayInd);
  2776     if (IsCop(Defend))
  2777       return;                   /* We don't want _more_ cops! */
  2778   }
  2779 
  2780   /* OK - let 'em have it... */
  2781   CopsAttackPlayer(Play);
  2782 }
  2783 
  2784 /* 
  2785  * Returns a suitable player (or cop) for "Play" to fire at. If "Play"
  2786  * is attacking a designated target already, return that, otherwise
  2787  * return the first valid opponent in the player's FightArray.
  2788  */
  2789 static Player *GetFireTarget(Player *Play)
  2790 {
  2791   Player *Defend;
  2792   guint ArrayInd;
  2793 
  2794   if (Play->Attacking
  2795       && g_slist_find(FirstServer, (gpointer)Play->Attacking)) {
  2796     return Play->Attacking;
  2797   } else {
  2798     Play->Attacking = NULL;
  2799     for (ArrayInd = 0; ArrayInd < Play->FightArray->len; ArrayInd++) {
  2800       Defend = (Player *)g_ptr_array_index(Play->FightArray, ArrayInd);
  2801       if (Defend && Defend != Play && IsOpponent(Play, Defend)) {
  2802         return Defend;
  2803       }
  2804     }
  2805   }
  2806   return NULL;
  2807 }
  2808 
  2809 static int GetArmor(Player *Play)
  2810 {
  2811   int Armor;
  2812 
  2813   if (IsCop(Play)) {
  2814     if (Play->Bitches.Carried == 0)
  2815       Armor = Cop[Play->CopIndex - 1].Armor;
  2816     else
  2817       Armor = Cop[Play->CopIndex - 1].DeputyArmor;
  2818   } else {
  2819     if (Play->Bitches.Carried == 0)
  2820       Armor = PlayerArmor;
  2821     else
  2822       Armor = BitchArmor;
  2823   }
  2824   if (Armor == 0)
  2825     Armor = 1;
  2826   return Armor;
  2827 }
  2828 
  2829 /* 
  2830  * Fires all weapons of player "Play" at an opponent, and resets
  2831  * the fight timeout (the reload time).
  2832  */
  2833 void Fire(Player *Play)
  2834 {
  2835   int Damage, i, j;
  2836   int AttackRating, DefendRating;
  2837   int BitchesKilled;
  2838   price_t Loot;
  2839   FightPoint fp;
  2840   Player *Defend;
  2841 
  2842   if (!Play->FightArray)
  2843     return;
  2844   if (!CanPlayerFire(Play))
  2845     return;
  2846 
  2847   AllowNextShooter(Play);
  2848   if (FightTimeout)
  2849     SetFightTimeout(Play);
  2850 
  2851   Defend = GetFireTarget(Play);
  2852   if (Defend) {
  2853     Damage = 0;
  2854     BitchesKilled = 0;
  2855     Loot = 0;
  2856     if (TotalGunsCarried(Play) > 0) {
  2857       GetFightRatings(Play, Defend, &AttackRating, &DefendRating);
  2858       if (brandom(0, AttackRating) > brandom(0, DefendRating)) {
  2859         fp = F_HIT;
  2860         for (i = 0; i < NumGun; i++)
  2861           for (j = 0; j < Play->Guns[i].Carried; j++) {
  2862             Damage += brandom(0, Gun[i].Damage);
  2863           }
  2864         Damage = Damage * 100 / GetArmor(Defend);
  2865         if (Damage == 0)
  2866           Damage = 1;
  2867         HandleDamage(Defend, Play, Damage, &BitchesKilled, &Loot);
  2868       } else
  2869         fp = F_MISS;
  2870     } else
  2871       fp = F_STAND;
  2872     SendFightMessage(Play, Defend, BitchesKilled, fp, Loot, TRUE, NULL);
  2873   }
  2874   CheckForKilledPlayers(Play);
  2875 
  2876   /* Careful, as we might have killed Player "Play" */
  2877   if (g_slist_find(FirstServer, (gpointer)Play))
  2878     DoReturnFire(Play);
  2879 
  2880   if (g_slist_find(FirstServer, (gpointer)Play))
  2881     CheckCopsIntervene(Play);
  2882 }
  2883 
  2884 gboolean CanPlayerFire(Player *Play)
  2885 {
  2886   return (FightTimeout == 0 || Play->FightTimeout == 0 ||
  2887           Play->FightTimeout <= time(NULL));
  2888 }
  2889 
  2890 gboolean CanRunHere(Player *Play)
  2891 {
  2892   return (Play->ResyncNum < E_ARRIVE && Play->ResyncNum != E_NONE);
  2893 }
  2894 
  2895 /* 
  2896  * To avoid boring waits, return the player who is next in line to be
  2897  * able to shoot (i.e. with the smallest FightTimeout) so that this
  2898  * player can be allowed to shoot immediately. If a player is already
  2899  * eligible to shoot, or there is a tie for "first place" then do
  2900  * nothing (i.e. return NULL).
  2901  */
  2902 Player *GetNextShooter(Player *Play)
  2903 {
  2904   Player *MinPlay, *Defend;
  2905   time_t MinTimeout;
  2906   guint mintie;
  2907   guint ArrayInd;
  2908 
  2909   if (!FightTimeout)
  2910     return NULL;
  2911 
  2912   MinPlay = NULL;
  2913   MinTimeout = 0;
  2914   mintie = 0;
  2915   for (ArrayInd = 0; ArrayInd < Play->FightArray->len; ArrayInd++) {
  2916     Defend = (Player *)g_ptr_array_index(Play->FightArray, ArrayInd);
  2917     if (Defend == Play)
  2918       continue;
  2919     if (Defend->FightTimeout == 0)
  2920       return NULL;
  2921     if (MinTimeout == 0 || Defend->FightTimeout < MinTimeout
  2922         || (Defend->FightTimeout == MinTimeout && Defend->tiebreak < mintie)) {
  2923       MinPlay = Defend;
  2924       MinTimeout = Defend->FightTimeout;
  2925       mintie = Defend->tiebreak;
  2926     }
  2927   }
  2928 
  2929   return MinPlay;
  2930 }
  2931 
  2932 void ResolveTipoff(Player *Play)
  2933 {
  2934   GString *text;
  2935 
  2936   if (IsCop(Play) || !CanRunHere(Play))
  2937     return;
  2938 
  2939   if (g_slist_find(FirstServer, (gpointer)Play->OnBehalfOf)) {
  2940     dopelog(4, LF_SERVER, _("%s: tipoff by %s finished OK."),
  2941             GetPlayerName(Play), GetPlayerName(Play->OnBehalfOf));
  2942     RemoveListPlayer(&(Play->TipList), Play->OnBehalfOf);
  2943     text = g_string_new("");
  2944     if (Play->Health == 0) {
  2945       g_string_printf(text,
  2946                        _("Following your tipoff, the cops ambushed %s, "
  2947                          "who was shot dead!"), GetPlayerName(Play));
  2948     } else {
  2949       dpg_string_printf(text,
  2950                          _("Following your tipoff, the cops ambushed %s, "
  2951                            "who escaped with %d %tde. "), GetPlayerName(Play),
  2952                          Play->Bitches.Carried, Names.Bitches);
  2953     }
  2954     GainBitch(Play->OnBehalfOf);
  2955     SendPlayerData(Play->OnBehalfOf);
  2956     SendPrintMessage(NULL, C_NONE, Play->OnBehalfOf, text->str);
  2957     g_string_free(text, TRUE);
  2958   }
  2959   Play->OnBehalfOf = NULL;
  2960 }
  2961 
  2962 /* 
  2963  * Cleans up combat after player "Play" has left.
  2964  */
  2965 void WithdrawFromCombat(Player *Play)
  2966 {
  2967   guint AttackInd, DefendInd;
  2968   gboolean FightDone;
  2969   Player *Attack, *Defend;
  2970   GSList *list;
  2971   gchar *text;
  2972 
  2973   for (list = FirstServer; list; list = g_slist_next(list)) {
  2974     Attack = (Player *)list->data;
  2975     if (Attack->Attacking == Play)
  2976       Attack->Attacking = NULL;
  2977   }
  2978 
  2979   if (!Play->FightArray)
  2980     return;
  2981 
  2982   ResolveTipoff(Play);
  2983   FightDone = TRUE;
  2984   for (AttackInd = 0; AttackInd < Play->FightArray->len; AttackInd++) {
  2985     Attack = (Player *)g_ptr_array_index(Play->FightArray, AttackInd);
  2986     for (DefendInd = 0; DefendInd < AttackInd; DefendInd++) {
  2987       Defend = (Player *)g_ptr_array_index(Play->FightArray, DefendInd);
  2988       if (Attack != Play && Defend != Play && IsOpponent(Attack, Defend)) {
  2989         FightDone = FALSE;
  2990         break;
  2991       }
  2992     }
  2993     if (!FightDone)
  2994       break;
  2995   }
  2996 
  2997   SendFightLeave(Play, FightDone);
  2998   g_ptr_array_remove(Play->FightArray, (gpointer)Play);
  2999 
  3000   if (FightDone) {
  3001     for (DefendInd = 0; DefendInd < Play->FightArray->len; DefendInd++) {
  3002       Defend = (Player *)g_ptr_array_index(Play->FightArray, DefendInd);
  3003       Defend->FightArray = NULL;
  3004       ResolveTipoff(Defend);
  3005       if (IsCop(Defend)) {
  3006         FirstServer = RemovePlayer(Defend, FirstServer);
  3007       } else if (Defend->Health == 0) {
  3008         FinishGame(Defend, _("You're dead! Game over."));
  3009       } else if (CanRunHere(Defend)
  3010                  && brandom(0, 100) > Location[Defend->IsAt].PolicePresence) {
  3011         Defend->EventNum = E_DOCTOR;
  3012         Defend->DocPrice = prandom(Bitch.MinPrice, Bitch.MaxPrice) *
  3013             Defend->Health / 500;
  3014         text =
  3015             dpg_strdup_printf(_
  3016                               ("YN^Do you pay a doctor %P to sew you up?"),
  3017                               Defend->DocPrice);
  3018         SendQuestion(NULL, C_ASKSEW, Defend, text);
  3019         g_free(text);
  3020       } else {
  3021         WaitForFightDone(Defend);
  3022       }
  3023     }
  3024     g_ptr_array_free(Play->FightArray, TRUE);
  3025   }
  3026   Play->FightArray = NULL;
  3027   Play->Attacking = NULL;
  3028 }
  3029 
  3030 /* 
  3031  * Inform player "To" of random offers or happenings. Returns 0 if
  3032  * the client can immediately be advanced to the next state, or 1
  3033  * there are first questions to be answered.
  3034  */
  3035 int RandomOffer(Player *To)
  3036 {
  3037   int r, amount, ind;
  3038   GString *text;
  3039 
  3040   r = brandom(0, 100);
  3041 
  3042   text = g_string_new(NULL);
  3043 
  3044   if (!Sanitized && (r < 10)) {
  3045     g_string_assign(text, _("You were mugged in the subway!"));
  3046     To->Cash = To->Cash * (price_t)brandom(80, 95) / 100l;
  3047   } else if (r < 30) {
  3048     amount = brandom(3, 7);
  3049     ind = IsCarryingRandom(To, amount);
  3050     if (ind == -1 && amount > To->CoatSize) {
  3051       g_string_free(text, TRUE);
  3052       return 0;
  3053     }
  3054     if (ind == -1) {
  3055       ind = brandom(0, NumDrug);
  3056       dpg_string_printf(text,
  3057                          _("You meet a friend! He gives you %d %tde."),
  3058                          amount, Drug[ind].Name);
  3059       To->Drugs[ind].Carried += amount;
  3060       To->CoatSize -= amount;
  3061     } else {
  3062       dpg_string_printf(text,
  3063                          _("You meet a friend! You give him %d %tde."),
  3064                          amount, Drug[ind].Name);
  3065       To->Drugs[ind].TotalValue =
  3066           To->Drugs[ind].TotalValue * (To->Drugs[ind].Carried -
  3067                                        amount) / To->Drugs[ind].Carried;
  3068       To->Drugs[ind].Carried -= amount;
  3069       To->CoatSize += amount;
  3070     }
  3071     SendPlayerData(To);
  3072     SendPrintMessage(NULL, C_NONE, To, text->str);
  3073   } else if (Sanitized) {
  3074     /* Debugging message: we would normally have a random drug-related
  3075        event here, but "Sanitized" mode is turned on */
  3076     dopelog(3, LF_SERVER, _("Sanitized away a RandomOffer"));
  3077   } else if (r < 50) {
  3078     amount = brandom(3, 7);
  3079     ind = IsCarryingRandom(To, amount);
  3080     if (ind != -1) {
  3081       dpg_string_printf(text, _("Police dogs chase you for %d blocks! "
  3082                                  "You dropped some %tde! That's a drag, man!"),
  3083                          brandom(3, 7), Names.Drugs);
  3084       To->Drugs[ind].TotalValue = To->Drugs[ind].TotalValue *
  3085           (To->Drugs[ind].Carried - amount) / To->Drugs[ind].Carried;
  3086       To->Drugs[ind].Carried -= amount;
  3087       To->CoatSize += amount;
  3088       SendPlayerData(To);
  3089       SendPrintMessage(NULL, C_NONE, To, text->str);
  3090     } else {
  3091       ind = brandom(0, NumDrug);
  3092       amount = brandom(3, 7);
  3093       if (amount > To->CoatSize) {
  3094         g_string_free(text, TRUE);
  3095         return 0;
  3096       }
  3097       dpg_string_printf(text,
  3098                          _("You find %d %tde on a dead dude in the subway!"),
  3099                          amount, Drug[ind].Name);
  3100       To->Drugs[ind].Carried += amount;
  3101       To->CoatSize -= amount;
  3102       SendPlayerData(To);
  3103       SendPrintMessage(NULL, C_NONE, To, text->str);
  3104     }
  3105   } else if (r < 60
  3106              && To->Drugs[WEED].Carried + To->Drugs[HASHISH].Carried > 0) {
  3107     ind = (To->Drugs[WEED].Carried >
  3108            To->Drugs[HASHISH].Carried) ? WEED : HASHISH;
  3109     amount = brandom(2, 6);
  3110     if (amount > To->Drugs[ind].Carried)
  3111       amount = To->Drugs[ind].Carried;
  3112     dpg_string_printf(text,
  3113                        _("Your mama made brownies with some of your %tde! "
  3114                          "They were great!"), Drug[ind].Name);
  3115     To->Drugs[ind].TotalValue = To->Drugs[ind].TotalValue *
  3116         (To->Drugs[ind].Carried - amount) / To->Drugs[ind].Carried;
  3117     To->Drugs[ind].Carried -= amount;
  3118     To->CoatSize += amount;
  3119     SendPlayerData(To);
  3120     SendPrintMessage(NULL, C_NONE, To, text->str);
  3121   } else if (r < 65) {
  3122     g_string_assign(text,
  3123                     _("YN^There is some weed that smells like paraquat "
  3124                      "here!^It looks good! Will you smoke it? "));
  3125     To->EventNum = E_WEED;
  3126     SendQuestion(NULL, C_NONE, To, text->str);
  3127     g_string_free(text, TRUE);
  3128     return 1;
  3129   } else if (NumStoppedTo > 0) {
  3130     g_string_printf(text, _("You stopped to %s."),
  3131                      StoppedTo[brandom(0, NumStoppedTo)]);
  3132     amount = brandom(1, 10);
  3133     if (To->Cash >= amount)
  3134       To->Cash -= amount;
  3135     SendPlayerData(To);
  3136     SendPrintMessage(NULL, C_NONE, To, text->str);
  3137   }
  3138   g_string_free(text, TRUE);
  3139   return 0;
  3140 }
  3141 
  3142 /* 
  3143  * Offers player "To" bitches/trenchcoats or guns. If ForceBitch is
  3144  * TRUE, then a bitch is definitely offered. Returns 0 if the client
  3145  * can advance immediately to the next state, 1 otherwise.
  3146  */
  3147 int OfferObject(Player *To, gboolean ForceBitch)
  3148 {
  3149   int ObjNum;
  3150   gchar *text = NULL;
  3151 
  3152   if (brandom(0, 100) < 50 || ForceBitch) {
  3153     if (WantAntique) {
  3154       To->Bitches.Price = prandom(MINTRENCHPRICE, MAXTRENCHPRICE);
  3155       text = dpg_strdup_printf(_("YN^Would you like to buy a bigger "
  3156                                  "trenchcoat for %P?"), To->Bitches.Price);
  3157     } else {
  3158       To->Bitches.Price =
  3159           prandom(Bitch.MinPrice, Bitch.MaxPrice) / (price_t)10;
  3160       text =
  3161           dpg_strdup_printf(_
  3162                             ("YN^Hey dude! I'll help carry your %tde for a "
  3163                              "mere %P. Yes or no?"), Names.Drugs,
  3164                             To->Bitches.Price);
  3165     }
  3166     SendQuestion(NULL, C_ASKBITCH, To, text);
  3167     g_free(text);
  3168     return 1;
  3169   } else if (!Sanitized && NumGun > 0
  3170              && (TotalGunsCarried(To) < To->Bitches.Carried + 2)) {
  3171     ObjNum = brandom(0, NumGun);
  3172     To->Guns[ObjNum].Price = Gun[ObjNum].Price / 10;
  3173     if (Gun[ObjNum].Space > To->CoatSize)
  3174       return 0;
  3175     text = dpg_strdup_printf(_("YN^Would you like to buy a %tde for %P?"),
  3176                              Gun[ObjNum].Name, To->Guns[ObjNum].Price);
  3177     SendQuestion(NULL, C_ASKGUN, To, text);
  3178     g_free(text);
  3179     return 1;
  3180   }
  3181   return 0;
  3182 }
  3183 
  3184 /* Whether a particular drug is especially cheap or expensive */
  3185 enum DealType {
  3186   DT_NORMAL, DT_CHEAP, DT_EXPENSIVE
  3187 };
  3188 
  3189 /* 
  3190  * Generates drug prices and drug busts etc. for player "To"
  3191  * "Deal" is an array of size NumDrug.
  3192  */
  3193 static void GenerateDrugsHere(Player *To, enum DealType *Deal)
  3194 {
  3195   int NumEvents, NumDrugs, NumRandom, i;
  3196 
  3197   for (i = 0; i < NumDrug; i++) {
  3198     To->Drugs[i].Price = 0;
  3199     Deal[i] = DT_NORMAL;
  3200   }
  3201   NumEvents = 0;
  3202   if (brandom(0, 100) < 70)
  3203     NumEvents = 1;
  3204   if (brandom(0, 100) < 40 && NumEvents == 1)
  3205     NumEvents = 2;
  3206   if (brandom(0, 100) < 5 && NumEvents == 2)
  3207     NumEvents = 3;
  3208   NumDrugs = 0;
  3209   while (NumEvents > 0) {
  3210     i = brandom(0, NumDrug);
  3211     if (Deal[i] != DT_NORMAL)
  3212       continue;
  3213     if (Drug[i].Expensive && (!Drug[i].Cheap || brandom(0, 100) < 50)) {
  3214       Deal[i] = DT_EXPENSIVE;
  3215       To->Drugs[i].Price = prandom(Drug[i].MinPrice, Drug[i].MaxPrice)
  3216           * Drugs.ExpensiveMultiply;
  3217       NumDrugs++;
  3218       NumEvents--;
  3219     } else if (Drug[i].Cheap) {
  3220       Deal[i] = DT_CHEAP;
  3221       To->Drugs[i].Price = prandom(Drug[i].MinPrice, Drug[i].MaxPrice)
  3222           / Drugs.CheapDivide;
  3223       NumDrugs++;
  3224       NumEvents--;
  3225     }
  3226   }
  3227   NumRandom = brandom(Location[To->IsAt].MinDrug,
  3228                       Location[To->IsAt].MaxDrug);
  3229   if (NumRandom > NumDrug)
  3230     NumRandom = NumDrug;
  3231 
  3232   NumDrugs = NumRandom - NumDrugs;
  3233   while (NumDrugs > 0) {
  3234     i = brandom(0, NumDrug);
  3235     if (To->Drugs[i].Price == 0) {
  3236       To->Drugs[i].Price = prandom(Drug[i].MinPrice, Drug[i].MaxPrice);
  3237       NumDrugs--;
  3238     }
  3239   }
  3240 }
  3241 
  3242 /* 
  3243  * Sends details of drug prices to player "To". If "DisplayBusts"
  3244  * is TRUE, also regenerates drug prices and sends details of
  3245  * special events such as drug busts.
  3246  */
  3247 void SendDrugsHere(Player *To, gboolean DisplayBusts)
  3248 {
  3249   int i;
  3250   enum DealType *Deal = NULL;
  3251   gchar *prstr;
  3252   GString *text;
  3253   gboolean First;
  3254 
  3255   Deal = g_new0(enum DealType, NumDrug);
  3256   if (DisplayBusts)
  3257     GenerateDrugsHere(To, Deal);
  3258 
  3259   text = g_string_new(NULL);
  3260   First = TRUE;
  3261   if (DisplayBusts) {
  3262     for (i = 0; i < NumDrug; i++) {
  3263       if (Deal[i] != DT_NORMAL) {
  3264         if (!First)
  3265           g_string_append_c(text, '^');
  3266         if (Deal[i] == DT_CHEAP) {
  3267           g_string_append(text, Drug[i].CheapStr);
  3268         } else {
  3269           dpg_string_append_printf(text, brandom(0, 100) < 50
  3270                               ? Drugs.ExpensiveStr1 : Drugs.ExpensiveStr2,
  3271                               Drug[i].Name);
  3272         }
  3273         First = FALSE;
  3274       }
  3275     }
  3276   }
  3277   g_free(Deal);
  3278 
  3279   if (!First)
  3280     SendPrintMessage(NULL, C_NONE, To, text->str);
  3281   g_string_truncate(text, 0);
  3282   for (i = 0; i < NumDrug; i++) {
  3283     g_string_append_printf(text, "%s^",
  3284                       (prstr = pricetostr(To->Drugs[i].Price)));
  3285     g_free(prstr);
  3286   }
  3287   SendServerMessage(NULL, C_NONE, C_DRUGHERE, To, text->str);
  3288   g_string_free(text, TRUE);
  3289 }
  3290 
  3291 /* 
  3292  * Handles the incoming message in "answer" from player "From" and
  3293  * intended for player "To".
  3294  */
  3295 void HandleAnswer(Player *From, Player *To, char *answer)
  3296 {
  3297   int i;
  3298   gchar *text;
  3299   Player *Defender;
  3300 
  3301   if (!From || From->EventNum == E_NONE)
  3302     return;
  3303   if (answer[0] == 'Y' && From->EventNum == E_OFFOBJECT
  3304       && From->Bitches.Price && From->Bitches.Price > From->Cash)
  3305     answer[0] = 'N';
  3306   if ((From->EventNum == E_FIGHT || From->EventNum == E_FIGHTASK)
  3307       && CanRunHere(From)) {
  3308     From->EventNum = E_FIGHT;
  3309     if (answer[0] == 'R' || answer[0] == 'Y') {
  3310       RunFromCombat(From, -1);
  3311     } else {
  3312       Fire(From);
  3313     }
  3314   } else if (answer[0] == 'Y')
  3315     switch (From->EventNum) {
  3316     case E_OFFOBJECT:
  3317       if (g_slist_find(FirstServer, (gpointer)From->OnBehalfOf)) {
  3318         dopelog(3, LF_SERVER, _("%s: offer was on behalf of %s"),
  3319                 GetPlayerName(From), GetPlayerName(From->OnBehalfOf));
  3320         if (From->Bitches.Price) {
  3321           text = dpg_strdup_printf(_("%s has accepted your %tde!"
  3322                                      "^Use the G key to contact your spy."),
  3323                                    GetPlayerName(From), Names.Bitch);
  3324           From->OnBehalfOf->Flags |= SPYINGON;
  3325           SendPlayerData(From->OnBehalfOf);
  3326           SendPrintMessage(NULL, C_NONE, From->OnBehalfOf, text);
  3327           g_free(text);
  3328           i = GetListEntry(&(From->SpyList), From->OnBehalfOf);
  3329           if (i >= 0)
  3330             From->SpyList.Data[i].Turns = 0;
  3331         }
  3332       }
  3333       if (From->Bitches.Price) {
  3334         text = g_strdup_printf("bitch^0^1");
  3335         BuyObject(From, text);
  3336         g_free(text);
  3337       } else {
  3338         for (i = 0; i < NumGun; i++)
  3339           if (From->Guns[i].Price) {
  3340             text = g_strdup_printf("gun^%d^1", i);
  3341             BuyObject(From, text);
  3342             g_free(text);
  3343             break;
  3344           }
  3345       }
  3346       From->OnBehalfOf = NULL;
  3347       From->EventNum++;
  3348       SendEvent(From);
  3349       break;
  3350     case E_LOANSHARK:
  3351       SendServerMessage(NULL, C_NONE, C_LOANSHARK, From, NULL);
  3352       break;
  3353     case E_BANK:
  3354       SendServerMessage(NULL, C_NONE, C_BANK, From, NULL);
  3355       break;
  3356     case E_GUNSHOP:
  3357       for (i = 0; i < NumGun; i++)
  3358         From->Guns[i].Price = Gun[i].Price;
  3359       SendServerMessage(NULL, C_NONE, C_GUNSHOP, From, NULL);
  3360       break;
  3361     case E_HIREBITCH:
  3362       text = g_strdup_printf("bitch^0^1");
  3363       BuyObject(From, text);
  3364       g_free(text);
  3365       From->EventNum++;
  3366       SendEvent(From);
  3367       break;
  3368     case E_ROUGHPUB:
  3369       From->EventNum++;
  3370       SendEvent(From);
  3371       break;
  3372     case E_WEED:
  3373       FinishGame(From, _("You hallucinated for three days on the wildest "
  3374                          "trip you ever imagined!^Then you died because "
  3375                          "your brain disintegrated!"));
  3376       break;
  3377     case E_DOCTOR:
  3378       if (From->Cash >= From->DocPrice) {
  3379         From->Cash -= From->DocPrice;
  3380         From->Health = 100;
  3381         SendPlayerData(From);
  3382       }
  3383       WaitForFightDone(From);
  3384       break;
  3385     default:
  3386       break;
  3387   } else if (From->EventNum == E_ARRIVE) {
  3388     if ((answer[0] == 'A' || answer[0] == 'T') &&
  3389         IsConnectedPlayer(From->OnBehalfOf) &&
  3390         g_slist_find(FirstServer, (gpointer)From->OnBehalfOf)) {
  3391       Defender = From->OnBehalfOf;
  3392       From->OnBehalfOf = NULL;  /* So we don't think it was a tipoff */
  3393       if (Defender->IsAt == From->IsAt) {
  3394         if (answer[0] == 'A') {
  3395           From->EventNum = Defender->EventNum = E_NONE;
  3396           AttackPlayer(From, Defender);
  3397         }
  3398       } else {
  3399         text = g_strdup_printf(_("Too late - %s has just left!"),
  3400                                GetPlayerName(Defender));
  3401         SendPrintMessage(NULL, C_MISSFIGHT, From, text);
  3402         g_free(text);
  3403         From->EventNum++;
  3404         SendEvent(From);
  3405       }
  3406     } else {
  3407       From->EventNum++;
  3408       SendEvent(From);
  3409     }
  3410     From->OnBehalfOf = NULL;
  3411   } else
  3412     switch (From->EventNum) {
  3413     case E_ROUGHPUB:
  3414       From->EventNum++;
  3415       From->EventNum++;
  3416       SendEvent(From);
  3417       break;
  3418     case E_DOCTOR:
  3419       WaitForFightDone(From);
  3420       break;
  3421     case E_HIREBITCH:
  3422     case E_GUNSHOP:
  3423     case E_BANK:
  3424     case E_LOANSHARK:
  3425     case E_OFFOBJECT:
  3426     case E_WEED:
  3427       if (g_slist_find(FirstServer, (gpointer)From->OnBehalfOf)) {
  3428         dopelog(3, LF_SERVER, _("%s: offer was on behalf of %s"),
  3429                 GetPlayerName(From), GetPlayerName(From->OnBehalfOf));
  3430         if (From->Bitches.Price && From->EventNum == E_OFFOBJECT) {
  3431           text = dpg_strdup_printf(_("%s has rejected your %tde!"),
  3432                                    GetPlayerName(From), Names.Bitch);
  3433           GainBitch(From->OnBehalfOf);
  3434           SendPlayerData(From->OnBehalfOf);
  3435           SendPrintMessage(NULL, C_NONE, From->OnBehalfOf, text);
  3436           g_free(text);
  3437           RemoveListPlayer(&(From->SpyList), From->OnBehalfOf);
  3438         }
  3439       }
  3440       From->EventNum++;
  3441       SendEvent(From);
  3442       break;
  3443     default:
  3444       break;
  3445     }
  3446 }
  3447 
  3448 /* 
  3449  * Processes a request stored in "data" from player "From" to buy an
  3450  * object (bitch, gun, or drug).
  3451  * Objects can be sold if the amount given in "data" is negative, and
  3452  * given away if their current price is zero.
  3453  */
  3454 void BuyObject(Player *From, char *data)
  3455 {
  3456   char *cp, *type;
  3457   int index, i, amount;
  3458 
  3459   cp = data;
  3460   type = GetNextWord(&cp, "");
  3461   index = GetNextInt(&cp, 0);
  3462   amount = GetNextInt(&cp, 0);
  3463   if (strcmp(type, "drug") == 0) {
  3464     if (index >= 0 && index < NumDrug
  3465         && From->Drugs[index].Carried + amount >= 0
  3466         && From->CoatSize - amount >= 0 && (From->Drugs[index].Price != 0
  3467                                             || amount < 0)
  3468         && From->Cash >= amount * From->Drugs[index].Price) {
  3469       if (amount > 0) {
  3470         From->Drugs[index].TotalValue += amount * From->Drugs[index].Price;
  3471       } else if (From->Drugs[index].Carried != 0) {
  3472         From->Drugs[index].TotalValue = From->Drugs[index].TotalValue *
  3473             (From->Drugs[index].Carried +
  3474              amount) / From->Drugs[index].Carried;
  3475       }
  3476       From->Drugs[index].Carried += amount;
  3477       From->CoatSize -= amount;
  3478       From->Cash -= amount * From->Drugs[index].Price;
  3479       SendPlayerData(From);
  3480 
  3481       if (!Sanitized && NumCop > 0 && NumGun > 0
  3482           && (From->Drugs[index].Price == 0 &&
  3483               brandom(0, 100) < Location[From->IsAt].PolicePresence)) {
  3484         gchar *text;
  3485 
  3486         text = dpg_strdup_printf(_("The cops spot you dropping %tde!"),
  3487                                  Names.Drugs);
  3488         SendPrintMessage(NULL, C_NONE, From, text);
  3489         g_free(text);
  3490         CopsAttackPlayer(From);
  3491       }
  3492     }
  3493   } else if (strcmp(type, "gun") == 0) {
  3494     if (index >= 0 && index < NumGun
  3495         && TotalGunsCarried(From) + amount >= 0
  3496         && TotalGunsCarried(From) + amount <= From->Bitches.Carried + 2
  3497         && From->Guns[index].Price != 0
  3498         && From->CoatSize - amount * Gun[index].Space >= 0
  3499         && From->Cash >= amount * From->Guns[index].Price) {
  3500       From->Guns[index].Carried += amount;
  3501       From->CoatSize -= amount * Gun[index].Space;
  3502       From->Cash -= amount * From->Guns[index].Price;
  3503       SendPlayerData(From);
  3504     }
  3505   } else if (strcmp(type, "bitch") == 0) {
  3506     if (From->Bitches.Carried + amount >= 0
  3507         && From->Bitches.Price != 0
  3508         && amount * From->Bitches.Price <= From->Cash) {
  3509       for (i = 0; i < amount; i++)
  3510         GainBitch(From);
  3511       if (amount > 0)
  3512         From->Cash -= amount * From->Bitches.Price;
  3513       SendPlayerData(From);
  3514     }
  3515   }
  3516 }
  3517 
  3518 /* 
  3519  * Clears the bitch and gun prices stored for player "Play".
  3520  */
  3521 void ClearPrices(Player *Play)
  3522 {
  3523   int i;
  3524 
  3525   Play->Bitches.Price = 0;
  3526   for (i = 0; i < NumGun; i++)
  3527     Play->Guns[i].Price = 0;
  3528 }
  3529 
  3530 /* 
  3531  * Gives player "Play" a new bitch (or larger trenchcoat).
  3532  */
  3533 void GainBitch(Player *Play)
  3534 {
  3535   Play->CoatSize += 10;
  3536   Play->Bitches.Carried++;
  3537 }
  3538 
  3539 /* 
  3540  * Loses one bitch belonging to player "Play". If drugs or guns are
  3541  * lost with the bitch, 1 is returned (0 otherwise) and the lost
  3542  * items are added to "Guns" and "Drugs" if non-NULL.
  3543  */
  3544 int LoseBitch(Player *Play, Inventory *Guns, Inventory *Drugs)
  3545 {
  3546   int losedrug = 0, i, num, drugslost;
  3547   int *GunIndex, tmp;
  3548   GunIndex = g_new(int, NumGun);
  3549 
  3550   ClearInventory(Guns, Drugs);
  3551   Play->CoatSize -= 10;
  3552   if (TotalGunsCarried(Play) > 0) {
  3553     if (brandom(0, 100) <
  3554         TotalGunsCarried(Play) * 100 / (Play->Bitches.Carried + 2)) {
  3555       for (i = 0; i < NumGun; i++)
  3556         GunIndex[i] = i;
  3557       for (i = 0; i < NumGun * 5; i++) {
  3558         num = brandom(0, NumGun - 1);
  3559         tmp = GunIndex[num + 1];
  3560         GunIndex[num + 1] = GunIndex[num];
  3561         GunIndex[num] = tmp;
  3562       }
  3563       for (i = 0; i < NumGun; i++) {
  3564         if (Play->Guns[GunIndex[i]].Carried > 0) {
  3565           Play->Guns[GunIndex[i]].Carried--;
  3566           losedrug = 1;
  3567           Play->CoatSize += Gun[GunIndex[i]].Space;
  3568           if (Guns)
  3569             Guns[GunIndex[i]].Carried++;
  3570           break;
  3571         }
  3572       }
  3573     }
  3574   }
  3575   for (i = 0; i < NumDrug; i++)
  3576     if (Play->Drugs[i].Carried > 0) {
  3577       num =
  3578           (int)((float)Play->Drugs[i].Carried /
  3579                 (Play->Bitches.Carried + 2.0) + 0.5);
  3580       if (num > 0) {
  3581         Play->Drugs[i].TotalValue = Play->Drugs[i].TotalValue *
  3582             (Play->Drugs[i].Carried - num) / Play->Drugs[i].Carried;
  3583         Play->Drugs[i].Carried -= num;
  3584         if (Drugs)
  3585           Drugs[i].Carried += num;
  3586         Play->CoatSize += num;
  3587         losedrug = 1;
  3588       }
  3589     }
  3590   while (Play->CoatSize < 0) {
  3591     drugslost = 0;
  3592     for (i = 0; i < NumDrug; i++) {
  3593       if (Play->Drugs[i].Carried > 0) {
  3594         losedrug = 1;
  3595         drugslost = 1;
  3596         Play->Drugs[i].TotalValue = Play->Drugs[i].TotalValue *
  3597             (Play->Drugs[i].Carried - 1) / Play->Drugs[i].Carried;
  3598         Play->Drugs[i].Carried--;
  3599         Play->CoatSize++;
  3600         if (Play->CoatSize >= 0)
  3601           break;
  3602       }
  3603     }
  3604     if (!drugslost)
  3605       for (i = 0; i < NumGun; i++) {
  3606         if (Play->Guns[i].Carried > 0) {
  3607           losedrug = 1;
  3608           Play->Guns[i].Carried--;
  3609           Play->CoatSize += Gun[i].Space;
  3610           if (Play->CoatSize >= 0)
  3611             break;
  3612         }
  3613       }
  3614   }
  3615   Play->Bitches.Carried--;
  3616   g_free(GunIndex);
  3617   return losedrug;
  3618 }
  3619 
  3620 /* 
  3621  * If fight timeouts are in force, sets the timeout for the given player.
  3622  */
  3623 void SetFightTimeout(Player *Play)
  3624 {
  3625   if (FightTimeout) {
  3626     Play->FightTimeout = time(NULL) + (time_t) FightTimeout;
  3627 
  3628     /* Make sure we have a higher tiebreak value than any other player with
  3629      * the same fight timeout (since FightTimeout only has second resolution,
  3630      * and possibly only microseconds have elapsed) */
  3631     Play->tiebreak = 0;
  3632     if (Play->FightTimeout) {
  3633       GSList *listpt;
  3634 
  3635       for (listpt = FirstServer; listpt; listpt = g_slist_next(listpt)) {
  3636         Player *listplay = (Player *)listpt->data;
  3637 
  3638         if (listplay && listplay != Play
  3639             && listplay->FightTimeout == Play->FightTimeout) {
  3640           Play->tiebreak = MAX(Play->tiebreak, listplay->tiebreak + 1);
  3641         }
  3642       }
  3643     }
  3644   } else {
  3645     Play->FightTimeout = 0;
  3646   }
  3647 }
  3648 
  3649 /* 
  3650  * Removes any fight timeout for the given player.
  3651  */
  3652 void ClearFightTimeout(Player *Play)
  3653 {
  3654   Play->FightTimeout = 0;
  3655 }
  3656 
  3657 /* 
  3658  * Given the time of a pending event in "timeout" and the current time in
  3659  * "timenow", updates "mintime" with the number of seconds to that event,
  3660  * unless "mintime" is already smaller (as long as it's not -1, which
  3661  * means "uninitialized"). Returns 1 if the timeout has already expired.
  3662  */
  3663 long AddTimeout(time_t timeout, time_t timenow, long *mintime)
  3664 {
  3665   if (timeout == 0)
  3666     return 0;
  3667   else if (timeout <= timenow)
  3668     return 1;
  3669   else {
  3670     if (*mintime < 0 || timeout - timenow < *mintime)
  3671       *mintime = timeout - timenow;
  3672     return 0;
  3673   }
  3674 }
  3675 
  3676 /* 
  3677  * Returns the number of seconds until the next scheduled event. If such
  3678  * an event has already expired, returns 0. If no events are pending,
  3679  * returns -1. "First" should point to a list of valid players.
  3680  */
  3681 long GetMinimumTimeout(GSList *First)
  3682 {
  3683   Player *Play;
  3684   GSList *list;
  3685   long mintime = -1;
  3686   time_t timenow;
  3687 
  3688   timenow = time(NULL);
  3689 #ifdef NETWORKING
  3690   curl_multi_timeout(MetaConn.multi, &mintime);
  3691 #endif
  3692   if (AddTimeout(MetaMinTimeout, timenow, &mintime))
  3693     return 0;
  3694   if (AddTimeout(MetaUpdateTimeout, timenow, &mintime))
  3695     return 0;
  3696   for (list = First; list; list = g_slist_next(list)) {
  3697     Play = (Player *)list->data;
  3698     if (AddTimeout(Play->FightTimeout, timenow, &mintime) ||
  3699         AddTimeout(Play->IdleTimeout, timenow, &mintime) ||
  3700         AddTimeout(Play->ConnectTimeout, timenow, &mintime))
  3701       return 0;
  3702   }
  3703   return mintime;
  3704 }
  3705 
  3706 /* 
  3707  * Given a list of players in "First", checks to see if any events
  3708  * have timed out, and if so, performs the necessary actions. The
  3709  * new start of the list is returned, since a player may be removed
  3710  * if their ConnectTimeout has expired.
  3711  */
  3712 GSList *HandleTimeouts(GSList *First)
  3713 {
  3714   GSList *list, *nextlist;
  3715   Player *Play;
  3716   time_t timenow;
  3717 
  3718   timenow = time(NULL);
  3719   if (MetaMinTimeout <= timenow) {
  3720     MetaMinTimeout = 0;
  3721     if (MetaPlayerPending) {
  3722       dopelog(3, LF_SERVER, _("Sending pending updates to the metaserver..."));
  3723       RegisterWithMetaServer(TRUE, TRUE, FALSE);
  3724     }
  3725   }
  3726   if (MetaUpdateTimeout != 0 && MetaUpdateTimeout <= timenow) {
  3727     dopelog(3, LF_SERVER, _("Sending reminder message to the metaserver..."));
  3728     RegisterWithMetaServer(TRUE, FALSE, FALSE);
  3729   }
  3730   list = First;
  3731   while (list) {
  3732     nextlist = g_slist_next(list);
  3733     Play = (Player *)list->data;
  3734     if (Play->IdleTimeout != 0 && Play->IdleTimeout <= timenow) {
  3735       Play->IdleTimeout = 0;
  3736       dopelog(1, LF_SERVER, _("Player removed due to idle timeout"));
  3737       SendPrintMessage(NULL, C_NONE, Play,
  3738                        "Disconnected due to idle timeout");
  3739       ClientLeftServer(Play);
  3740       /* Blank the name, so that CountPlayers ignores this player */
  3741       SetPlayerName(Play, NULL);
  3742       /* Make sure they do actually disconnect, eventually! */
  3743       if (ConnectTimeout) {
  3744         Play->ConnectTimeout = time(NULL) + (time_t) ConnectTimeout;
  3745       }
  3746     } else if (Play->ConnectTimeout != 0
  3747                && Play->ConnectTimeout <= timenow) {
  3748       Play->ConnectTimeout = 0;
  3749       dopelog(1, LF_SERVER, _("Player removed due to connect timeout"));
  3750       First = RemovePlayer(Play, First);
  3751     } else if (IsConnectedPlayer(Play) &&
  3752                Play->FightTimeout != 0 && Play->FightTimeout <= timenow) {
  3753       ClearFightTimeout(Play);
  3754       if (IsCop(Play))
  3755         Fire(Play);
  3756       else
  3757         SendFightReload(Play);
  3758     }
  3759     list = nextlist;
  3760   }
  3761   return First;
  3762 }