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 }