tmessage.c - vaccinewars - be a doctor and try to vaccinate the world
 (HTM) git clone git://src.adamsgaard.dk/vaccinewars
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       tmessage.c (43453B)
       ---
            1 /************************************************************************
            2  * message.c      Message-handling routines for 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 <config.h>
           25 #endif
           26 
           27 #ifdef HAVE_UNISTD_H
           28 #include <unistd.h>
           29 #endif
           30 
           31 #ifndef CYGWIN
           32 #include <sys/types.h>
           33 #include <sys/socket.h>
           34 #endif
           35 
           36 #include <string.h>
           37 #include <stdlib.h>
           38 #include <glib.h>
           39 
           40 #include "convert.h"
           41 #include "dopewars.h"
           42 #include "message.h"
           43 #include "network.h"
           44 #include "nls.h"
           45 #include "serverside.h"
           46 #include "sound.h"
           47 #include "tstring.h"
           48 #include "util.h"
           49 
           50 /* Maximum sizes (in bytes) of read and write buffers - connections should
           51  * be dropped if either buffer is filled */
           52 #define MAXREADBUF   (32768)
           53 #define MAXWRITEBUF  (65536)
           54 
           55 /* *INDENT-OFF* */
           56 /* dopewars is built around a client-server model. Each client handles the
           57    user interface, but all the important calculation and processing is
           58    handled by the server. All communication is conducted via. TCP by means
           59    of plain text newline-delimited messages.
           60 
           61    New message structure:-
           62        Other^ACData
           63 
           64    Other:   The ID of the player at the "other end" of the connection;
           65             for messages received by the client, this is the player from
           66             which the message originates, while for messages received by
           67             the server, it's the player to which to deliver the message.
           68    A:       One-letter code; used by AI players to identify the message subtype
           69                                        (check AIPlayer.h)
           70    C:       One-letter code to identify the message type (check message.h)
           71    Data:    Message-dependent information
           72 
           73 
           74    For compatibility with old clients and servers, the old message structure
           75    is still supported:-
           76        From^To^ACData
           77 
           78    From,To:  Player names identifying the sender and intended recipient of
           79              the message. Either field may be blank, although the server will
           80              usually reject incoming messages if they are not properly
           81              identified with a correct "From" field.
           82    A,C,Data: As above, for the new message structure
           83 
           84    For example, a common message is the "printmessage" message (message code 
           85    C is C_PRINTMESSAGE), which simply instructs the client to display "Data". 
           86    Any ^ characters within Data are replaced by newlines on output. So in order 
           87    for the server to instruct player "Fred" (ID 1) to display "Hello world" it
           88    would send the message:-
           89        ^AAHello world                   (new format)
           90        ^Fred^AAHello world              (old format)
           91    Note that the server has left the Other (or From) field blank, and has
           92    specified the AI code 'A' - defined in AIPlayer.h as C_NONE (i.e. an
           93    "unimportant" message) as well as the main code 'A', defined as
           94    C_PRINTMESSAGE in message.h. Note also that the destination player (Fred)
           95    is not specified in the new format; the player is identified by the socket
           96    through which the message is transmitted.
           97 
           98    When the network is down, a server is simulated locally. Messages from
           99    the client are passed directly to the server message handling routine,
          100    and vice versa.  */
          101 /* *INDENT-ON* */
          102 
          103 GSList *FirstClient = NULL;
          104 
          105 static Converter *netconv = NULL;
          106 
          107 void (*ClientMessageHandlerPt)(char *, Player *) = NULL;
          108 
          109 /* 
          110  * Sends a message from player "From" to player "To" via. the server.
          111  * AI, Code and Data define the message.
          112  */
          113 void SendClientMessage(Player *From, AICode AI, MsgCode Code,
          114                        Player *To, char *Data)
          115 {
          116   DoSendClientMessage(From, AI, Code, To, Data, From);
          117 }
          118 
          119 /* 
          120  * Sends a message from player "From" to player "To" via. the server,
          121  * sending a blank name for "From" (this is required with the old message
          122  * format, up to and including the first successful C_NAME message, but has
          123  * no effect with the new format. AI, Code and Data define the message.
          124  */
          125 void SendNullClientMessage(Player *From, AICode AI, MsgCode Code,
          126                            Player *To, char *Data)
          127 {
          128   DoSendClientMessage(NULL, AI, Code, To, Data, From);
          129 }
          130 
          131 /* 
          132  * Send a message from client player "From" with computer code "AI",
          133  * human-readable code "Code" and data "Data". The message is sent to the
          134  * server, identifying itself as for "To". "BufOwn" identifies the player
          135  * which owns the buffers used for the actual wire connection. With the
          136  * new message format, "From" is ignored. From, To, or Data may be NULL.
          137  */
          138 void DoSendClientMessage(Player *From, AICode AI, MsgCode Code,
          139                          Player *To, char *Data, Player *BufOwn)
          140 {
          141   GString *text;
          142   Player *ServerFrom;
          143 
          144   g_assert(BufOwn != NULL);
          145   text = g_string_new(NULL);
          146   if (HaveAbility(BufOwn, A_PLAYERID)) {
          147     if (To)
          148       g_string_append_printf(text, "%d", To->ID);
          149     g_string_append_printf(text, "^%c%c%s", AI, Code, Data ? Data : "");
          150   } else {
          151     g_string_printf(text, "%s^%s^%c%c%s", From ? GetPlayerName(From) : "",
          152                      To ? GetPlayerName(To) : "", AI, Code,
          153                      Data ? Data : "");
          154   }
          155 #ifdef NETWORKING
          156   if (!Network) {
          157 #endif
          158     if (From)
          159       ServerFrom = GetPlayerByName(GetPlayerName(From), FirstServer);
          160     else if (FirstServer)
          161       ServerFrom = (Player *)(FirstServer->data);
          162     else {
          163       ServerFrom = g_new(Player, 1);
          164       FirstServer = AddPlayer(0, ServerFrom, FirstServer);
          165     }
          166     HandleServerMessage(text->str, ServerFrom);
          167 #ifdef NETWORKING
          168   } else {
          169     QueuePlayerMessageForSend(BufOwn, text->str);
          170   }
          171 #endif /* NETWORKING */
          172   g_string_free(text, TRUE);
          173 }
          174 
          175 /* 
          176  * Shorthand for the server sending a "printmessage"; instructs the
          177  * client "To" to display "Data".
          178  */
          179 void SendPrintMessage(Player *From, AICode AI, Player *To, char *Data)
          180 {
          181   SendServerMessage(From, AI, C_PRINTMESSAGE, To, Data);
          182 }
          183 
          184 /* 
          185  * Shorthand for the server sending a "question"; instructs the client
          186  * "To" to display the second word of Data and accept any letter within
          187  * the first word of Data as suitable reply.
          188  */
          189 void SendQuestion(Player *From, AICode AI, Player *To, char *Data)
          190 {
          191   SendServerMessage(From, AI, C_QUESTION, To, Data);
          192 }
          193 
          194 /* 
          195  * Sends a message from the server to client player "To" with computer
          196  * code "AI", human-readable code "Code" and data "Data", claiming
          197  * to be from player "From".
          198  */
          199 void SendServerMessage(Player *From, AICode AI, MsgCode Code,
          200                        Player *To, char *Data)
          201 {
          202   GString *text;
          203 
          204   if (IsCop(To))
          205     return;
          206   text = g_string_new(NULL);
          207   if (HaveAbility(To, A_PLAYERID)) {
          208     if (From)
          209       g_string_append_printf(text, "%d", From->ID);
          210     g_string_append_printf(text, "^%c%c%s", AI, Code, Data ? Data : "");
          211   } else {
          212     g_string_printf(text, "%s^%s^%c%c%s", From ? GetPlayerName(From) : "",
          213                      To ? GetPlayerName(To) : "", AI, Code,
          214                      Data ? Data : "");
          215   }
          216 #ifdef NETWORKING
          217   if (!Network) {
          218 #endif
          219     if (ClientMessageHandlerPt)
          220       (*ClientMessageHandlerPt)(text->str, (Player *)(FirstClient->data));
          221 #ifdef NETWORKING
          222   } else {
          223     QueuePlayerMessageForSend(To, text->str);
          224   }
          225 #endif
          226   g_string_free(text, TRUE);
          227 }
          228 
          229 /* 
          230  * Sets up the "abilities" of player "Play". Abilities are used to extend
          231  * the protocol; if both the client and server share an ability, then the
          232  * protocol extension can be used.
          233  */
          234 void InitAbilities(Player *Play)
          235 {
          236   int i;
          237 
          238   /* Clear all abilities */
          239   for (i = 0; i < A_NUM; i++) {
          240     Play->Abil.Local[i] = FALSE;
          241     Play->Abil.Remote[i] = FALSE;
          242     Play->Abil.Shared[i] = FALSE;
          243   }
          244   Play->Abil.RemoteNum = 0;
          245 
          246   /* Set local abilities; abilities that are client-dependent (e.g.
          247    * A_NEWFIGHT) can be overridden by individual clients if required, by
          248    * calling SetAbility, prior to calling SendAbilities */
          249   Play->Abil.Local[A_PLAYERID] = TRUE;
          250   Play->Abil.Local[A_NEWFIGHT] = TRUE;
          251   Play->Abil.Local[A_DRUGVALUE] = (DrugValue ? TRUE : FALSE);
          252   Play->Abil.Local[A_TSTRING] = TRUE;
          253   Play->Abil.Local[A_DONEFIGHT] = TRUE;
          254   Play->Abil.Local[A_UTF8] = TRUE;
          255   Play->Abil.Local[A_DATE] = TRUE;
          256 
          257   if (!Network) {
          258     for (i = 0; i < A_NUM; i++) {
          259       Play->Abil.Remote[i] = Play->Abil.Shared[i] = Play->Abil.Local[i];
          260     }
          261     Play->Abil.RemoteNum = A_NUM;
          262   }
          263 }
          264 
          265 /* 
          266  * Sends abilities of player "Play" to the other end of the client-server
          267  * connection.
          268  */
          269 void SendAbilities(Player *Play)
          270 {
          271   int i;
          272   gchar Data[A_NUM + 1];
          273 
          274   if (!Network)
          275     return;
          276   for (i = 0; i < A_NUM; i++) {
          277     Data[i] = (Play->Abil.Local[i] ? '1' : '0');
          278   }
          279   Data[A_NUM] = '\0';
          280   if (Server) {
          281     SendServerMessage(NULL, C_NONE, C_ABILITIES, Play, Data);
          282   } else {
          283     SendClientMessage(Play, C_NONE, C_ABILITIES, NULL, Data);
          284   }
          285 }
          286 
          287 /* 
          288  * Fills in the "remote" abilities of player "Play" using the message data
          289  * in "Data". These are the abilities of the server/client at the other
          290  * end of the connection.
          291  */
          292 void ReceiveAbilities(Player *Play, gchar *Data)
          293 {
          294   int i, Length;
          295 
          296   InitAbilities(Play);
          297   if (!Network)
          298     return;
          299   Play->Abil.RemoteNum = strlen(Data);
          300   Length = MIN(Play->Abil.RemoteNum, A_NUM);
          301   for (i = 0; i < Length; i++) {
          302     Play->Abil.Remote[i] = (Data[i] == '1' ? TRUE : FALSE);
          303   }
          304 }
          305 
          306 /* 
          307  * Combines the local and remote abilities of player "Play". The resulting
          308  * shared abilities are used to determine when protocol extensions can be
          309  * used.
          310  */
          311 void CombineAbilities(Player *Play)
          312 {
          313   int i;
          314 
          315   for (i = 0; i < A_NUM; i++) {
          316     Play->Abil.Shared[i] = (Play->Abil.Remote[i] && Play->Abil.Local[i]);
          317   }
          318 
          319   if (HaveAbility(Play, A_UTF8)) {
          320     Conv_SetCodeset(netconv, "UTF-8");
          321   }
          322 }
          323 
          324 /* 
          325  * Sets ability "Type" of player "Play", and also sets shared abilities if
          326  * networking is not active (the local server should support all abilities
          327  * that the client uses). Call this function prior to calling SendAbilities
          328  * so that the ability is recognised properly when networking _is_ active
          329  */
          330 void SetAbility(Player *Play, gint Type, gboolean Set)
          331 {
          332   if (Type < 0 || Type >= A_NUM)
          333     return;
          334   Play->Abil.Local[Type] = Set;
          335   if (!Network) {
          336     Play->Abil.Remote[Type] = Play->Abil.Shared[Type] =
          337         Play->Abil.Local[Type];
          338   }
          339 }
          340 
          341 /* 
          342  * Returns TRUE if ability "Type" is one of player "Play"'s shared abilities
          343  */
          344 gboolean HaveAbility(Player *Play, gint Type)
          345 {
          346   if (Type < 0 || Type >= A_NUM)
          347     return FALSE;
          348   else
          349     return (Play->Abil.Shared[Type]);
          350 }
          351 
          352 #ifdef NETWORKING
          353 /* 
          354  * Reads and writes player data from/to the network if it is ready.
          355  * If any data were read, TRUE is returned. "DoneOK" is set TRUE
          356  * unless a fatal error (i.e. the connection was broken) occurred.
          357  */
          358 gboolean PlayerHandleNetwork(Player *Play, gboolean ReadReady,
          359                              gboolean WriteReady, gboolean ErrorReady,
          360                              gboolean *DoneOK)
          361 {
          362   gboolean DataWaiting = FALSE;
          363 
          364   *DoneOK = TRUE;
          365   if (!Play)
          366     return DataWaiting;
          367   DataWaiting =
          368       NetBufHandleNetwork(&Play->NetBuf, ReadReady, WriteReady, ErrorReady,
          369                           DoneOK);
          370 
          371   return DataWaiting;
          372 }
          373 
          374 gchar *GetWaitingPlayerMessage(Player *Play)
          375 {
          376   gchar *unconv, *conv;
          377 
          378   unconv = GetWaitingMessage(&Play->NetBuf);
          379   if (unconv && Conv_Needed(netconv)) {
          380     conv = Conv_ToInternal(netconv, unconv, -1);
          381     g_free(unconv);
          382     return conv;
          383   } else {
          384     return unconv;
          385   }
          386 }
          387 
          388 gboolean ReadPlayerDataFromWire(Player *Play)
          389 {
          390   return ReadDataFromWire(&Play->NetBuf);
          391 }
          392 
          393 void QueuePlayerMessageForSend(Player *Play, gchar *data)
          394 {
          395   if (Conv_Needed(netconv)) {
          396     gchar *conv = Conv_ToExternal(netconv, data, -1);
          397     QueueMessageForSend(&Play->NetBuf, conv);
          398     g_free(conv);
          399   } else {
          400     QueueMessageForSend(&Play->NetBuf, data);
          401   }
          402 }
          403 
          404 gboolean WritePlayerDataToWire(Player *Play)
          405 {
          406   return WriteDataToWire(&Play->NetBuf);
          407 }
          408 
          409 gboolean OpenMetaHttpConnection(CurlConnection *conn, GError **err)
          410 {
          411   gboolean ret;
          412   gchar *url;
          413 
          414   url = g_strdup_printf("%s?output=text&getlist=%d",
          415                         MetaServer.URL, METAVERSION);
          416   ret = OpenCurlConnection(conn, url, NULL, err);
          417   g_free(url);
          418   return ret;
          419 }
          420 
          421 GQuark dope_meta_error_quark(void)
          422 {
          423   return g_quark_from_static_string("dope-meta-error-quark");
          424 }
          425 
          426 gboolean HandleWaitingMetaServerData(CurlConnection *conn, GSList **listpt,
          427                                      GError **err)
          428 {
          429   char *msg;
          430 
          431   g_assert(conn && listpt);
          432 
          433   msg = conn->data;
          434   /* This should be the first line of the body, the "MetaServer:" line */
          435   if (msg && strlen(msg) >= 14 && strncmp(msg, "FATAL ERROR:", 12) == 0) {
          436     g_set_error(err, DOPE_META_ERROR, DOPE_META_ERROR_INTERNAL,
          437                 _("Internal metaserver error \"%s\""), &msg[13]);
          438     return FALSE;
          439   } else if (msg && strncmp(msg, "MetaServer:", 11) != 0) {
          440     g_set_error(err, DOPE_META_ERROR, DOPE_META_ERROR_BAD_REPLY,
          441                 _("Bad metaserver reply \"%s\""), msg);
          442     return FALSE;
          443   }
          444 
          445   msg = CurlNextLine(conn, msg);
          446   while (msg) {
          447     char *name, *port, *version, *curplayers, *maxplayers, *update,
          448          *comment, *upsince;
          449     name = msg;
          450     port = CurlNextLine(conn, name);
          451     version = CurlNextLine(conn, port);
          452     curplayers = CurlNextLine(conn, version);
          453     maxplayers = CurlNextLine(conn, curplayers);
          454     update = CurlNextLine(conn, maxplayers);
          455     comment = CurlNextLine(conn, update);
          456     upsince = CurlNextLine(conn, comment);
          457     msg = CurlNextLine(conn, upsince);
          458     if (msg) {
          459       ServerData *NewServer = g_new0(ServerData, 1);
          460       NewServer->Name = g_strdup(name);
          461       NewServer->Port = atoi(port);
          462       NewServer->Version = g_strdup(version);
          463       NewServer->CurPlayers = curplayers[0] ? atoi(curplayers) : -1;
          464       NewServer->MaxPlayers = atoi(maxplayers);
          465       NewServer->Update = g_strdup(update);
          466       NewServer->Comment = g_strdup(comment);
          467       NewServer->UpSince = g_strdup(upsince);
          468       *listpt = g_slist_append(*listpt, NewServer);
          469     }
          470   }
          471   if (!*listpt) {
          472     g_set_error_literal(err, DOPE_META_ERROR, DOPE_META_ERROR_EMPTY,
          473                         _("No servers listed on metaserver"));
          474     return FALSE;
          475   }
          476   return TRUE;
          477 }
          478 
          479 void ClearServerList(GSList **listpt)
          480 {
          481   ServerData *ThisServer;
          482 
          483   while (*listpt) {
          484     ThisServer = (ServerData *)((*listpt)->data);
          485     g_free(ThisServer->Name);
          486     g_free(ThisServer->Comment);
          487     g_free(ThisServer->Version);
          488     g_free(ThisServer->Update);
          489     g_free(ThisServer->UpSince);
          490     g_free(ThisServer);
          491     *listpt = g_slist_remove(*listpt, ThisServer);
          492   }
          493 }
          494 #endif /* NETWORKING */
          495 
          496 /* 
          497  * Removes a terminating newline from "str", if one is present.
          498  */
          499 void chomp(char *str)
          500 {
          501   int len = strlen(str);
          502 
          503   if (str[len - 1] == '\n')
          504     str[len - 1] = 0;
          505 }
          506 
          507 /* 
          508  * Adds the plain text string "unenc" to the end of the GString "str",
          509  * replacing "special" characters in the same way as the
          510  * application/x-www-form-urlencoded media type, suitable for sending
          511  * to CGI scripts etc.
          512  */
          513 void AddURLEnc(GString *str, gchar *unenc)
          514 {
          515   guint i;
          516 
          517   if (!unenc || !str)
          518     return;
          519   for (i = 0; i < strlen(unenc); i++) {
          520     if ((unenc[i] >= 'a' && unenc[i] <= 'z') ||
          521         (unenc[i] >= 'A' && unenc[i] <= 'Z') ||
          522         (unenc[i] >= '0' && unenc[i] <= '9') ||
          523         unenc[i] == '-' || unenc[i] == '_' || unenc[i] == '.') {
          524       g_string_append_c(str, unenc[i]);
          525     } else if (unenc[i] == ' ') {
          526       g_string_append_c(str, '+');
          527     } else {
          528       g_string_append_printf(str, "%%%02X", unenc[i]);
          529     }
          530   }
          531 }
          532 
          533 /* 
          534  * Sends the message made up of AI,Code and Data to all players except
          535  * "Except" (if non-NULL). It will be sent by the server, and on behalf of
          536  * player "From".
          537  */
          538 void BroadcastToClients(AICode AI, MsgCode Code, char *Data,
          539                         Player *From, Player *Except)
          540 {
          541   Player *tmp;
          542   GSList *list;
          543 
          544   for (list = FirstServer; list; list = g_slist_next(list)) {
          545     tmp = (Player *)list->data;
          546     if (IsConnectedPlayer(tmp) && tmp != Except) {
          547       SendServerMessage(From, AI, Code, tmp, Data);
          548     }
          549   }
          550 }
          551 
          552 /* 
          553  * Encodes an Inventory structure into a string, and sends it as the data
          554  * with a server message constructed from the other arguments.
          555  */
          556 void SendInventory(Player *From, AICode AI, MsgCode Code,
          557                    Player *To, Inventory *Guns, Inventory *Drugs)
          558 {
          559   int i;
          560   GString *text;
          561 
          562   text = g_string_new(NULL);
          563   for (i = 0; i < NumGun; i++) {
          564     g_string_append_printf(text, "%d:", Guns ? Guns[i].Carried : 0);
          565   }
          566   for (i = 0; i < NumDrug; i++) {
          567     g_string_append_printf(text, "%d:", Drugs ? Drugs[i].Carried : 0);
          568   }
          569   SendServerMessage(From, AI, Code, To, text->str);
          570   g_string_free(text, TRUE);
          571 }
          572 
          573 /* 
          574  * Decodes a string representation (in "Data") to its original Inventory
          575  * contents, and stores it in "Guns" and "Drugs" if non-NULL.
          576  */
          577 void ReceiveInventory(char *Data, Inventory *Guns, Inventory *Drugs)
          578 {
          579   int i, val;
          580   char *pt;
          581 
          582   pt = Data;
          583   for (i = 0; i < NumGun; i++) {
          584     val = GetNextInt(&pt, 0);
          585     if (Guns)
          586       Guns[i].Carried = val;
          587   }
          588   for (i = 0; i < NumDrug; i++) {
          589     val = GetNextInt(&pt, 0);
          590     if (Drugs)
          591       Drugs[i].Carried = val;
          592   }
          593 }
          594 
          595 /* 
          596  * Sends all pertinent data about player "To" from the server
          597  * to player "To".
          598  */
          599 void SendPlayerData(Player *To)
          600 {
          601   SendSpyReport(To, To);
          602 }
          603 
          604 /* 
          605  * Sends pertinent data about player "SpiedOn" from the server
          606  * to player "To".
          607  */
          608 void SendSpyReport(Player *To, Player *SpiedOn)
          609 {
          610   gchar *cashstr, *debtstr, *bankstr;
          611   GString *text;
          612   int i;
          613 
          614   text = g_string_new(NULL);
          615   g_string_printf(text, "%s^%s^%s^%d^%d^%d^%d^%d^",
          616                    (cashstr = pricetostr(SpiedOn->Cash)),
          617                    (debtstr = pricetostr(SpiedOn->Debt)),
          618                    (bankstr = pricetostr(SpiedOn->Bank)),
          619                    SpiedOn->Health, SpiedOn->CoatSize,
          620                    SpiedOn->IsAt, SpiedOn->Turn, SpiedOn->Flags);
          621   g_free(cashstr);
          622   g_free(debtstr);
          623   g_free(bankstr);
          624   if (HaveAbility(SpiedOn, A_DATE)) {
          625     g_string_append_printf(text, "%d^%d^%d^", g_date_get_day(SpiedOn->date),
          626                       g_date_get_month(SpiedOn->date),
          627                       g_date_get_year(SpiedOn->date));
          628   }
          629   for (i = 0; i < NumGun; i++) {
          630     g_string_append_printf(text, "%d^", SpiedOn->Guns[i].Carried);
          631   }
          632   for (i = 0; i < NumDrug; i++) {
          633     g_string_append_printf(text, "%d^", SpiedOn->Drugs[i].Carried);
          634   }
          635   if (HaveAbility(To, A_DRUGVALUE))
          636     for (i = 0; i < NumDrug; i++) {
          637       g_string_append_printf(text, "%s^",
          638                         (cashstr =
          639                          pricetostr(SpiedOn->Drugs[i].TotalValue)));
          640       g_free(cashstr);
          641     }
          642   g_string_append_printf(text, "%d", SpiedOn->Bitches.Carried);
          643   if (To != SpiedOn)
          644     SendServerMessage(SpiedOn, C_NONE, C_UPDATE, To, text->str);
          645   else
          646     SendServerMessage(NULL, C_NONE, C_UPDATE, To, text->str);
          647   g_string_free(text, TRUE);
          648 }
          649 
          650 #define NUMNAMES 11
          651 
          652 void SendInitialData(Player *To)
          653 {
          654   gchar *LocalNames[NUMNAMES] = { Names.Bitch, Names.Bitches, Names.Gun,
          655     Names.Guns, Names.Drug, Names.Drugs,
          656     Names.Date, Names.LoanSharkName,
          657     Names.BankName, Names.GunShopName,
          658     Names.RoughPubName
          659   };
          660   gint i;
          661   GString *text;
          662 
          663   if (!Network)
          664     return;
          665   if (!HaveAbility(To, A_TSTRING))
          666     for (i = 0; i < NUMNAMES; i++) {
          667       LocalNames[i] = GetDefaultTString(LocalNames[i]);
          668     }
          669   text = g_string_new("");
          670   g_string_printf(text, "%s^%d^%d^%d^", VERSION, NumLocation, NumGun,
          671                    NumDrug);
          672   for (i = 0; i < 6; i++) {
          673     g_string_append(text, LocalNames[i]);
          674     g_string_append_c(text, '^');
          675   }
          676 
          677   if (HaveAbility(To, A_DATE)) {
          678     g_string_append(text, LocalNames[6]);
          679     g_string_append_c(text, '^');
          680   } else {
          681     g_string_append_printf(text, "%d-^-%d^", StartDate.month, StartDate.year);
          682   }
          683 
          684   if (HaveAbility(To, A_PLAYERID))
          685     g_string_append_printf(text, "%d^", To->ID);
          686 
          687   /* Player ID is expected after the first 7 names, so send the rest now */
          688   for (i = 7; i < NUMNAMES; i++) {
          689     g_string_append(text, LocalNames[i]);
          690     g_string_append_c(text, '^');
          691   }
          692 
          693   if (!HaveAbility(To, A_TSTRING))
          694     for (i = 0; i < NUMNAMES; i++) {
          695       g_free(LocalNames[i]);
          696     }
          697 
          698   g_string_append_printf(text, "%c%s^", Currency.Prefix ? '1' : '0',
          699                     Currency.Symbol);
          700   SendServerMessage(NULL, C_NONE, C_INIT, To, text->str);
          701   g_string_free(text, TRUE);
          702 }
          703 
          704 void ReceiveInitialData(Player *Play, char *Data)
          705 {
          706   char *pt, *curr;
          707   GSList *list;
          708 
          709   pt = Data;
          710   GetNextWord(&pt, "(unknown)"); /* server version */
          711   ResizeLocations(GetNextInt(&pt, NumLocation));
          712   ResizeGuns(GetNextInt(&pt, NumGun));
          713   ResizeDrugs(GetNextInt(&pt, NumDrug));
          714   for (list = FirstClient; list; list = g_slist_next(list)) {
          715     UpdatePlayer((Player *)list->data);
          716   }
          717   AssignName(&Names.Bitch, GetNextWord(&pt, ""));
          718   AssignName(&Names.Bitches, GetNextWord(&pt, ""));
          719   AssignName(&Names.Gun, GetNextWord(&pt, ""));
          720   AssignName(&Names.Guns, GetNextWord(&pt, ""));
          721   AssignName(&Names.Drug, GetNextWord(&pt, ""));
          722   AssignName(&Names.Drugs, GetNextWord(&pt, ""));
          723   if (HaveAbility(Play, A_DATE)) {
          724     AssignName(&Names.Date, GetNextWord(&pt, ""));
          725   } else {
          726     gchar *month, *year, *date;
          727     month = GetNextWord(&pt, "");
          728     year = GetNextWord(&pt, "");
          729 
          730     date = g_strdup_printf("%s%%T%s", month, year);
          731     AssignName(&Names.Date, date);
          732     g_free(date);
          733   }
          734   if (HaveAbility(Play, A_PLAYERID))
          735     Play->ID = GetNextInt(&pt, 0);
          736 
          737   /* Servers up to version 1.4.8 don't send the following names, so
          738    * default to the existing values if they haven't been sent */
          739   AssignName(&Names.LoanSharkName, GetNextWord(&pt, Names.LoanSharkName));
          740   AssignName(&Names.BankName, GetNextWord(&pt, Names.BankName));
          741   AssignName(&Names.GunShopName, GetNextWord(&pt, Names.GunShopName));
          742   AssignName(&Names.RoughPubName, GetNextWord(&pt, Names.RoughPubName));
          743 
          744   /* Currency data are only sent by versions >= 1.5.3 */
          745   curr = GetNextWord(&pt, NULL);
          746   if (curr && strlen(curr) >= 1) {
          747     Currency.Prefix = (curr[0] == '1');
          748     AssignName(&Currency.Symbol, &curr[1]);
          749   }
          750 }
          751 
          752 void SendMiscData(Player *To)
          753 {
          754   gchar *text, *prstr[2], *LocalName;
          755   int i;
          756   gboolean HaveTString;
          757 
          758   if (!Network)
          759     return;
          760   HaveTString = HaveAbility(To, A_TSTRING);
          761   text = g_strdup_printf("0^%c%s^%s^", DT_PRICES,
          762                          (prstr[0] = pricetostr(Prices.Spy)),
          763                          (prstr[1] = pricetostr(Prices.Tipoff)));
          764   SendServerMessage(NULL, C_NONE, C_DATA, To, text);
          765   g_free(prstr[0]);
          766   g_free(prstr[1]);
          767   g_free(text);
          768   for (i = 0; i < NumGun; i++) {
          769     if (HaveTString)
          770       LocalName = Gun[i].Name;
          771     else
          772       LocalName = GetDefaultTString(Gun[i].Name);
          773     text = g_strdup_printf("%d^%c%s^%s^%d^%d^", i, DT_GUN, LocalName,
          774                            (prstr[0] = pricetostr(Gun[i].Price)),
          775                            Gun[i].Space, Gun[i].Damage);
          776     if (!HaveTString)
          777       g_free(LocalName);
          778     SendServerMessage(NULL, C_NONE, C_DATA, To, text);
          779     g_free(prstr[0]);
          780     g_free(text);
          781   }
          782   for (i = 0; i < NumDrug; i++) {
          783     if (HaveTString)
          784       LocalName = Drug[i].Name;
          785     else
          786       LocalName = GetDefaultTString(Drug[i].Name);
          787     text = g_strdup_printf("%d^%c%s^%s^%s^", i, DT_DRUG, LocalName,
          788                            (prstr[0] = pricetostr(Drug[i].MinPrice)),
          789                            (prstr[1] = pricetostr(Drug[i].MaxPrice)));
          790     if (!HaveTString)
          791       g_free(LocalName);
          792     SendServerMessage(NULL, C_NONE, C_DATA, To, text);
          793     g_free(prstr[0]);
          794     g_free(prstr[1]);
          795     g_free(text);
          796   }
          797   for (i = 0; i < NumLocation; i++) {
          798     if (HaveTString)
          799       LocalName = Location[i].Name;
          800     else
          801       LocalName = GetDefaultTString(Location[i].Name);
          802     text = g_strdup_printf("%d^%c%s^", i, DT_LOCATION, LocalName);
          803     if (!HaveTString)
          804       g_free(LocalName);
          805     SendServerMessage(NULL, C_NONE, C_DATA, To, text);
          806     g_free(text);
          807   }
          808 }
          809 
          810 /* 
          811  * Decodes information about locations, drugs, prices, etc. in "Data"
          812  */
          813 void ReceiveMiscData(char *Data)
          814 {
          815   char *pt, *Name, Type;
          816   int i;
          817 
          818   pt = Data;
          819   i = GetNextInt(&pt, 0);
          820   Name = GetNextWord(&pt, "");
          821   Type = Name[0];
          822   if (strlen(Name) > 1) {
          823     switch (Type) {
          824     case DT_LOCATION:
          825       if (i >= 0 && i < NumLocation) {
          826         AssignName(&Location[i].Name, &Name[1]);
          827         Location[i].PolicePresence = 10;
          828         Location[i].MinDrug = NumDrug / 2 + 1;
          829         Location[i].MaxDrug = NumDrug;
          830       }
          831       break;
          832     case DT_GUN:
          833       if (i >= 0 && i < NumGun) {
          834         AssignName(&Gun[i].Name, &Name[1]);
          835         Gun[i].Price = GetNextPrice(&pt, (price_t)0);
          836         Gun[i].Space = GetNextInt(&pt, 0);
          837         Gun[i].Damage = GetNextInt(&pt, 0);
          838       }
          839       break;
          840     case DT_DRUG:
          841       if (i >= 0 && i < NumDrug) {
          842         AssignName(&Drug[i].Name, &Name[1]);
          843         Drug[i].MinPrice = GetNextPrice(&pt, (price_t)0);
          844         Drug[i].MaxPrice = GetNextPrice(&pt, (price_t)0);
          845       }
          846       break;
          847     case DT_PRICES:
          848       Prices.Spy = strtoprice(&Name[1]);
          849       Prices.Tipoff = GetNextPrice(&pt, (price_t)0);
          850       break;
          851     }
          852   }
          853 }
          854 
          855 /* 
          856  * Decode player data from the string "text" into player "From"; "Play"
          857  * specifies the player that owns the network connection.
          858  */
          859 void ReceivePlayerData(Player *Play, char *text, Player *From)
          860 {
          861   char *cp;
          862   int i;
          863 
          864   cp = text;
          865   From->Cash = GetNextPrice(&cp, (price_t)0);
          866   From->Debt = GetNextPrice(&cp, (price_t)0);
          867   From->Bank = GetNextPrice(&cp, (price_t)0);
          868   From->Health = GetNextInt(&cp, 100);
          869   From->CoatSize = GetNextInt(&cp, 0);
          870   From->IsAt = GetNextInt(&cp, 0);
          871   From->Turn = GetNextInt(&cp, 0);
          872   From->Flags = GetNextInt(&cp, 0);
          873   if (HaveAbility(Play, A_DATE)) {
          874     g_date_set_day(From->date, GetNextInt(&cp, 1));
          875     g_date_set_month(From->date, GetNextInt(&cp, 1));
          876     g_date_set_year(From->date, GetNextInt(&cp, 1980));
          877   }
          878   for (i = 0; i < NumGun; i++) {
          879     From->Guns[i].Carried = GetNextInt(&cp, 0);
          880   }
          881   for (i = 0; i < NumDrug; i++) {
          882     From->Drugs[i].Carried = GetNextInt(&cp, 0);
          883   }
          884   if (HaveAbility(Play, A_DRUGVALUE)) {
          885     for (i = 0; i < NumDrug; i++) {
          886       From->Drugs[i].TotalValue = GetNextPrice(&cp, (price_t)0);
          887     }
          888   }
          889   From->Bitches.Carried = GetNextInt(&cp, 0);
          890 }
          891 
          892 gchar *GetNextWord(gchar **Data, gchar *Default)
          893 {
          894   gchar *Word;
          895 
          896   if (*Data == NULL || **Data == '\0')
          897     return Default;
          898   Word = *Data;
          899   while (**Data != '\0' && **Data != '^')
          900     (*Data)++;
          901   if (**Data != '\0') {
          902     **Data = '\0';
          903     (*Data)++;
          904   }
          905   return Word;
          906 }
          907 
          908 void AssignNextWord(gchar **Data, gchar **Dest)
          909 {
          910   if (!Dest)
          911     return;
          912   g_free(*Dest);
          913   *Dest = g_strdup(GetNextWord(Data, ""));
          914 }
          915 
          916 int GetNextInt(gchar **Data, int Default)
          917 {
          918   gchar *Word = GetNextWord(Data, NULL);
          919 
          920   if (Word)
          921     return atoi(Word);
          922   else
          923     return Default;
          924 }
          925 
          926 price_t GetNextPrice(gchar **Data, price_t Default)
          927 {
          928   gchar *Word = GetNextWord(Data, NULL);
          929 
          930   if (Word)
          931     return strtoprice(Word);
          932   else
          933     return Default;
          934 }
          935 
          936 /* 
          937  * Called when the client is pushed off the server, or the server
          938  * terminates. Using the client information, starts a local server
          939  * to reproduce the current game situation as best as possible so
          940  * that the game can be continued in single player mode.
          941  */
          942 void SwitchToSinglePlayer(Player *Play)
          943 {
          944   if (Network && Client && FirstClient) {
          945     Player *NewPlayer;
          946 
          947     ShutdownNetwork(Play);
          948     CleanUpServer();
          949     Network = Server = Client = FALSE;
          950     InitAbilities(Play);
          951     NewPlayer = g_new(Player, 1);
          952     FirstServer = AddPlayer(0, NewPlayer, FirstServer);
          953     CopyPlayer(NewPlayer, Play);
          954     NewPlayer->Flags = 0;
          955     NewPlayer->EventNum = E_ARRIVE;
          956     SendEvent(NewPlayer);
          957   }
          958 }
          959 
          960 void InitNetwork(void)
          961 {
          962   netconv = Conv_New();
          963 #ifdef NETWORKING
          964   StartNetworking();
          965 #endif
          966 }
          967 
          968 /* 
          969  * Closes down the client side of the network connection. Clears the list
          970  * of client players (with the exception of "you", the player "Play"),
          971  * and closes the network socket.
          972  */
          973 void ShutdownNetwork(Player *Play)
          974 {
          975   if (Play != FirstClient->data) {
          976     g_error("Oops! FirstClient should be player!");
          977   }
          978   while (g_slist_next(FirstClient)) {
          979     FirstClient = RemovePlayer((Player *)g_slist_next(FirstClient)->data,
          980                                FirstClient);
          981   }
          982 #ifdef NETWORKING
          983   ShutdownNetworkBuffer(&Play->NetBuf);
          984 #endif
          985   Client = Network = Server = FALSE;
          986 }
          987 
          988 /* 
          989  * Given a "raw" message in "Msg" and a pointer to the start of the linked
          990  * list of known players in "First", sets the other arguments to the message
          991  * fields. Data is returned as a pointer into the message "Msg", and should
          992  * therefore NOT be g_free'd. "Play" is a pointer to the player which is
          993  * receiving the message. "Other" is the player that is identified by the
          994  * message; for messages to clients, this will be the player "From" which
          995  * the message claims to be, while for messages to servers, this will be
          996  * the player "To" which to send messages. Returns 0 on success, -1 on failure.
          997  */
          998 int ProcessMessage(char *Msg, Player *Play, Player **Other, AICode *AI,
          999                    MsgCode *Code, char **Data, GSList *First)
         1000 {
         1001   gchar *pt, *buf;
         1002   guint ID;
         1003 
         1004   if (!First || !Play)
         1005     return -1;
         1006 
         1007   *AI = C_NONE;
         1008   *Code = C_PRINTMESSAGE;
         1009   *Other = &Noone;
         1010   pt = Msg;
         1011   if (HaveAbility(Play, A_PLAYERID)) {
         1012     buf = GetNextWord(&pt, NULL);
         1013     if (buf && buf[0]) {
         1014       ID = atoi(buf);
         1015       *Other = GetPlayerByID(ID, First);
         1016     }
         1017   } else {
         1018     buf = GetNextWord(&pt, NULL);
         1019     if (Client)
         1020       *Other = GetPlayerByName(buf, First);
         1021     buf = GetNextWord(&pt, NULL);
         1022     if (Server)
         1023       *Other = GetPlayerByName(buf, First);
         1024   }
         1025   if (!(*Other))
         1026     return -1;
         1027 
         1028   if (strlen(pt) >= 2) {
         1029     *AI = pt[0];
         1030     *Code = pt[1];
         1031     *Data = &pt[2];
         1032     return 0;
         1033   }
         1034   return -1;
         1035 }
         1036 
         1037 /* 
         1038  * Decodes the message data "text" into a list of drug prices for
         1039  * player "To"
         1040  */
         1041 void ReceiveDrugsHere(char *text, Player *To)
         1042 {
         1043   char *cp;
         1044   int i;
         1045 
         1046   To->EventNum = E_ARRIVE;
         1047   cp = text;
         1048   for (i = 0; i < NumDrug; i++) {
         1049     To->Drugs[i].Price = GetNextPrice(&cp, (price_t)0);
         1050   }
         1051 }
         1052 
         1053 /* 
         1054  * Handles messages that both human clients and AI players deal with
         1055  * in the same way.
         1056  */
         1057 gboolean HandleGenericClientMessage(Player *From, AICode AI, MsgCode Code,
         1058                                     Player *To, char *Data,
         1059                                     DispMode *DisplayMode)
         1060 {
         1061   Player *tmp;
         1062   gchar *pt;
         1063 
         1064   switch (Code) {
         1065   case C_LIST:
         1066   case C_JOIN:
         1067     tmp = g_new(Player, 1);
         1068 
         1069     FirstClient = AddPlayer(0, tmp, FirstClient);
         1070     pt = Data;
         1071     SetPlayerName(tmp, GetNextWord(&pt, NULL));
         1072     if (HaveAbility(To, A_PLAYERID))
         1073       tmp->ID = GetNextInt(&pt, 0);
         1074     break;
         1075   case C_DATA:
         1076     ReceiveMiscData(Data);
         1077     break;
         1078   case C_INIT:
         1079     ReceiveInitialData(To, Data);
         1080     break;
         1081   case C_ABILITIES:
         1082     ReceiveAbilities(To, Data);
         1083     CombineAbilities(To);
         1084     break;
         1085   case C_LEAVE:
         1086     if (From != &Noone)
         1087       FirstClient = RemovePlayer(From, FirstClient);
         1088     break;
         1089   case C_TRADE:
         1090     if (DisplayMode)
         1091       *DisplayMode = DM_DEAL;
         1092     break;
         1093   case C_DRUGHERE:
         1094     ReceiveDrugsHere(Data, To);
         1095     if (DisplayMode)
         1096       *DisplayMode = DM_STREET;
         1097     break;
         1098   case C_FIGHTPRINT:
         1099     if (From != &Noone) {
         1100       From->Flags |= FIGHTING;
         1101       To->Flags |= CANSHOOT;
         1102     }
         1103     if (DisplayMode)
         1104       *DisplayMode = DM_FIGHT;
         1105     break;
         1106   case C_CHANGEDISP:
         1107     if (DisplayMode) {
         1108       if (Data[0] == 'N' && *DisplayMode == DM_STREET)
         1109         *DisplayMode = DM_NONE;
         1110       if (Data[0] == 'Y' && *DisplayMode == DM_NONE)
         1111         *DisplayMode = DM_STREET;
         1112     }
         1113     break;
         1114   default:
         1115     return FALSE;
         1116     break;
         1117   }
         1118   return TRUE;
         1119 }
         1120 
         1121 void SendFightReload(Player *To)
         1122 {
         1123   SendFightMessage(To, NULL, 0, F_RELOAD, (price_t)0, FALSE, NULL);
         1124 }
         1125 
         1126 void SendOldCanFireMessage(Player *To, GString *text)
         1127 {
         1128   if (To->EventNum == E_FIGHT) {
         1129     To->EventNum = E_FIGHTASK;
         1130     if (CanRunHere(To) && To->Health > 0 && !HaveAbility(To, A_NEWFIGHT)) {
         1131       if (text->len > 0)
         1132         g_string_append_c(text, '^');
         1133       if (TotalGunsCarried(To) == 0) {
         1134         g_string_prepend(text, "YN^");
         1135         g_string_append(text, _("Do you run?"));
         1136       } else {
         1137         g_string_prepend(text, "RF^");
         1138         g_string_append(text, _("Do you run, or fight?"));
         1139       }
         1140       SendQuestion(NULL, C_NONE, To, text->str);
         1141     } else {
         1142       SendOldFightPrint(To, text, FALSE);
         1143     }
         1144   }
         1145 }
         1146 
         1147 void SendOldFightPrint(Player *To, GString *text, gboolean FightOver)
         1148 {
         1149   gboolean Fighting, CanShoot;
         1150 
         1151   Fighting = !FightOver;
         1152   CanShoot = CanPlayerFire(To);
         1153 
         1154   To->Flags &= ~(CANSHOOT + FIGHTING);
         1155   if (Fighting)
         1156     To->Flags |= FIGHTING;
         1157   if (Fighting && CanShoot)
         1158     To->Flags |= CANSHOOT;
         1159   SendPlayerData(To);
         1160   To->Flags &= ~(CANSHOOT + FIGHTING);
         1161 
         1162   SendServerMessage(NULL, C_NONE, C_FIGHTPRINT, To, text->str);
         1163 }
         1164 
         1165 void SendFightLeave(Player *Play, gboolean FightOver)
         1166 {
         1167   SendFightMessage(Play, NULL, 0, FightOver ? F_LASTLEAVE : F_LEAVE,
         1168                    (price_t)0, TRUE, NULL);
         1169 }
         1170 
         1171 void ReceiveFightMessage(gchar *Data, gchar **AttackName,
         1172                          gchar **DefendName, int *DefendHealth,
         1173                          int *DefendBitches, gchar **BitchName,
         1174                          int *BitchesKilled, int *ArmPercent,
         1175                          FightPoint *fp, gboolean *CanRunHere,
         1176                          gboolean *Loot, gboolean *CanFire,
         1177                          gchar **Message)
         1178 {
         1179   gchar *pt, *Flags;
         1180 
         1181   pt = Data;
         1182   *AttackName = GetNextWord(&pt, "");
         1183   *DefendName = GetNextWord(&pt, "");
         1184   *DefendHealth = GetNextInt(&pt, 0);
         1185   *DefendBitches = GetNextInt(&pt, 0);
         1186   *BitchName = GetNextWord(&pt, "");
         1187   *BitchesKilled = GetNextInt(&pt, 0);
         1188   *ArmPercent = GetNextInt(&pt, 0);
         1189 
         1190   Flags = GetNextWord(&pt, NULL);
         1191   if (Flags && strlen(Flags) >= 4) {
         1192     *fp = Flags[0];
         1193     *CanRunHere = (Flags[1] == '1');
         1194     *Loot = (Flags[2] == '1');
         1195     *CanFire = (Flags[3] == '1');
         1196   } else {
         1197     *fp = F_MSG;
         1198     *CanRunHere = *Loot = *CanFire = FALSE;
         1199   }
         1200   *Message = pt;
         1201 
         1202   switch (*fp) {
         1203   case F_HIT:
         1204     SoundPlay(Sounds.FightHit);
         1205     if (*BitchesKilled > 0) {
         1206       SoundPlay(*DefendName[0] ? Sounds.EnemyBitchKilled : Sounds.BitchKilled);
         1207     }
         1208     if (*DefendHealth <= 0) {
         1209       SoundPlay(*DefendName[0] ? Sounds.EnemyKilled : Sounds.Killed);
         1210     }
         1211     break;
         1212   case F_MISS:
         1213     SoundPlay(Sounds.FightMiss);
         1214     break;
         1215   case F_RELOAD:
         1216     SoundPlay(Sounds.FightReload);
         1217     break;
         1218   case F_FAILFLEE:
         1219     SoundPlay(*AttackName[0] ? Sounds.EnemyFailFlee : Sounds.FailFlee);
         1220     break;
         1221   case F_LEAVE:
         1222   case F_LASTLEAVE:
         1223     SoundPlay(*AttackName[0] ? Sounds.EnemyFlee : Sounds.Flee);
         1224     break;
         1225   default:
         1226     break;
         1227   }
         1228 }
         1229 
         1230 void SendFightMessage(Player *Attacker, Player *Defender,
         1231                       int BitchesKilled, FightPoint fp,
         1232                       price_t Loot, gboolean Broadcast, gchar *Msg)
         1233 {
         1234   guint ArrayInd;
         1235   int ArmPercent, Damage, MaxDamage, i;
         1236   Player *To;
         1237   GString *text;
         1238   gchar *BitchName;
         1239 
         1240   if (!Attacker->FightArray)
         1241     return;
         1242 
         1243   MaxDamage = Damage = 0;
         1244   for (i = 0; i < NumGun; i++) {
         1245     if (Gun[i].Damage > MaxDamage)
         1246       MaxDamage = Gun[i].Damage;
         1247     Damage += Gun[i].Damage * Attacker->Guns[i].Carried;
         1248   }
         1249   MaxDamage *= (Attacker->Bitches.Carried + 2);
         1250   ArmPercent = Damage * 100 / MaxDamage;
         1251 
         1252   text = g_string_new("");
         1253 
         1254   for (ArrayInd = 0; ArrayInd < Attacker->FightArray->len; ArrayInd++) {
         1255     To = (Player *)g_ptr_array_index(Attacker->FightArray, ArrayInd);
         1256     if (!Broadcast && To != Attacker)
         1257       continue;
         1258     g_string_truncate(text, 0);
         1259     if (HaveAbility(To, A_NEWFIGHT)) {
         1260       if (Defender) {
         1261         if (IsCop(Defender)) {
         1262           if (Defender->Bitches.Carried == 1) {
         1263             BitchName = Cop[Defender->CopIndex - 1].DeputyName;
         1264           } else {
         1265             BitchName = Cop[Defender->CopIndex - 1].DeputiesName;
         1266           }
         1267         } else {
         1268           if (Defender->Bitches.Carried == 1) {
         1269             BitchName = Names.Bitch;
         1270           } else {
         1271             BitchName = Names.Bitches;
         1272           }
         1273         }
         1274       } else
         1275         BitchName = "";
         1276       g_string_printf(text, "%s^%s^%d^%d^%s^%d^%d^%c%c%c%c^",
         1277                        Attacker == To ? "" : GetPlayerName(Attacker),
         1278                        (Defender == To || Defender == NULL)
         1279                        ? "" : GetPlayerName(Defender),
         1280                        Defender ? Defender->Health : 0,
         1281                        Defender ? Defender->Bitches.Carried : 0,
         1282                        BitchName,
         1283                        BitchesKilled, ArmPercent,
         1284                        fp, CanRunHere(To) ? '1' : '0',
         1285                        Loot ? '1' : '0',
         1286                        fp != F_ARRIVED && fp != F_LASTLEAVE &&
         1287                        CanPlayerFire(To) ? '1' : '0');
         1288     }
         1289     if (Msg) {
         1290       g_string_append(text, Msg);
         1291     } else {
         1292       FormatFightMessage(To, text, Attacker, Defender, BitchesKilled,
         1293                          ArmPercent, fp, Loot);
         1294     }
         1295     if (HaveAbility(To, A_NEWFIGHT)) {
         1296       SendServerMessage(NULL, C_NONE, C_FIGHTPRINT, To, text->str);
         1297     } else if (CanRunHere(To)) {
         1298       if (fp != F_ARRIVED && fp != F_MSG &&
         1299           fp != F_LASTLEAVE &&
         1300           (fp != F_LEAVE || Attacker != To) &&
         1301           CanPlayerFire(To) && To->EventNum == E_FIGHT) {
         1302         SendOldCanFireMessage(To, text);
         1303       } else if (text->len > 0)
         1304         SendPrintMessage(NULL, C_NONE, To, text->str);
         1305     } else {
         1306       SendOldFightPrint(To, text, fp == F_LASTLEAVE);
         1307     }
         1308   }
         1309   g_string_free(text, TRUE);
         1310 }
         1311 
         1312 void FormatFightMessage(Player *To, GString *text, Player *Attacker,
         1313                         Player *Defender, int BitchesKilled,
         1314                         int ArmPercent, FightPoint fp, price_t Loot)
         1315 {
         1316   gchar *Armament, *DefendName, *AttackName;
         1317   int Health, Bitches;
         1318   gchar *BitchName, *BitchesName;
         1319 
         1320   if (Defender && IsCop(Defender)) {
         1321     BitchName = Cop[Defender->CopIndex - 1].DeputyName;
         1322     BitchesName = Cop[Defender->CopIndex - 1].DeputiesName;
         1323   } else {
         1324     BitchName = Names.Bitch;
         1325     BitchesName = Names.Bitches;
         1326   }
         1327 
         1328   AttackName = (!Attacker
         1329                 || Attacker == To ? "" : GetPlayerName(Attacker));
         1330   DefendName = (!Defender
         1331                 || Defender == To ? "" : GetPlayerName(Defender));
         1332   Health = Defender ? Defender->Health : 0;
         1333   Bitches = Defender ? Defender->Bitches.Carried : 0;
         1334 
         1335   switch (fp) {
         1336   case F_ARRIVED:
         1337     Armament = ArmPercent < 10 ? _("pitifully armed") :
         1338         ArmPercent < 25 ? _("lightly armed") :
         1339         ArmPercent < 60 ? _("moderately well armed") :
         1340         ArmPercent < 80 ? _("heavily armed") : _("armed to the teeth");
         1341     if (DefendName[0]) {
         1342       if (IsCop(Defender) && !AttackName[0]) {
         1343         if (Bitches == 0) {
         1344           dpg_string_append_printf(text, _("%s - %s - is chasing you, man!"),
         1345                               DefendName, Armament);
         1346         } else {
         1347           dpg_string_append_printf(text,
         1348                               _("%s and %d %tde - %s - are chasing you, man!"),
         1349                               DefendName, Bitches, BitchesName, Armament);
         1350         }
         1351       } else {
         1352         dpg_string_append_printf(text, _("%s arrives with %d %tde, %s!"),
         1353                             DefendName, Bitches, BitchesName, Armament);
         1354       }
         1355     }
         1356     break;
         1357   case F_STAND:
         1358     if (AttackName[0]) {
         1359       g_string_append_printf(text, _("%s stands and takes it"), AttackName);
         1360     } else {
         1361       g_string_append(text, _("You stand there like a dummy."));
         1362     }
         1363     break;
         1364   case F_FAILFLEE:
         1365     if (AttackName[0]) {
         1366       g_string_append_printf(text, _("%s tries to get away, but fails."),
         1367                         AttackName);
         1368     } else {
         1369       g_string_append(text, _("Panic! You can't get away!"));
         1370     }
         1371     break;
         1372   case F_LEAVE:
         1373   case F_LASTLEAVE:
         1374     if (Attacker->Health > 0) {
         1375       if (AttackName[0]) {
         1376         if (!IsCop(Attacker) && brandom(0, 100) < 70
         1377             && Attacker->IsAt >= 0) {
         1378           dpg_string_append_printf(text, _("%s has got away to %tde!"), AttackName,
         1379                               Location[Attacker->IsAt].Name);
         1380         } else {
         1381           g_string_append_printf(text, _("%s has got away!"), AttackName);
         1382         }
         1383       } else {
         1384         g_string_append_printf(text, _("You got away!"));
         1385       }
         1386     }
         1387     break;
         1388   case F_RELOAD:
         1389     if (!AttackName[0]) {
         1390       g_string_append(text, _("Guns reloaded..."));
         1391     }
         1392     break;
         1393   case F_MISS:
         1394     if (AttackName[0] && DefendName[0]) {
         1395       g_string_append_printf(text, _("%s shoots at %s... and misses!"),
         1396                         AttackName, DefendName);
         1397     } else if (AttackName[0]) {
         1398       g_string_append_printf(text, _("%s shoots at you... and misses!"),
         1399                         AttackName);
         1400     } else if (DefendName[0]) {
         1401       g_string_append_printf(text, _("You missed %s!"), DefendName);
         1402     }
         1403     break;
         1404   case F_HIT:
         1405     if (AttackName[0] && DefendName[0]) {
         1406       if (Health == 0 && Bitches == 0) {
         1407         g_string_append_printf(text, _("%s shoots %s dead."),
         1408                           AttackName, DefendName);
         1409       } else if (BitchesKilled) {
         1410         dpg_string_append_printf(text, _("%s shoots at %s and kills a %tde!"),
         1411                             AttackName, DefendName, BitchName);
         1412       } else {
         1413         g_string_append_printf(text, _("%s shoots at %s."),
         1414                           AttackName, DefendName);
         1415       }
         1416     } else if (AttackName[0]) {
         1417       if (Health == 0 && Bitches == 0) {
         1418         g_string_append_printf(text, _("%s wasted you, man! What a drag!"),
         1419                           AttackName);
         1420       } else if (BitchesKilled) {
         1421         dpg_string_append_printf(text,
         1422                             _("%s shoots at you... and kills a %tde!"),
         1423                             AttackName, BitchName);
         1424       } else {
         1425         g_string_append_printf(text, _("%s hits you, man!"), AttackName);
         1426       }
         1427     } else if (DefendName[0]) {
         1428       if (Health == 0 && Bitches == 0) {
         1429         g_string_append_printf(text, _("You killed %s!"), DefendName);
         1430       } else if (BitchesKilled) {
         1431         dpg_string_append_printf(text, _("You hit %s, and killed a %tde!"),
         1432                             DefendName, BitchName);
         1433       } else {
         1434         g_string_append_printf(text, _("You hit %s!"), DefendName);
         1435       }
         1436       if (Loot > 0) {
         1437         dpg_string_append_printf(text, _(" You find %P on the body!"), Loot);
         1438       } else if (Loot < 0) {
         1439         g_string_append(text, _(" You loot the body!"));
         1440       }
         1441     }
         1442     break;
         1443   case F_MSG:
         1444     break;
         1445   }
         1446 }