| tmessage.c - vaccinewars - be a doctor and try to vaccinate the world | |
| git clone git://src.adamsgaard.dk/vaccinewars | |
| Log | |
| Files | |
| Refs | |
| README | |
| LICENSE | |
| --- | |
| tmessage.c (43453B) | |
| --- | |
| 1 /************************************************************************ | |
| 2 * message.c Message-handling routines for dopewars * | |
| 3 * Copyright (C) 1998-2021 Ben Webb * | |
| 4 * Email: [email protected] * | |
| 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 shou… | |
| 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 t… | |
| 57 user interface, but all the important calculation and processing is | |
| 58 handled by the server. All communication is conducted via. TCP by mea… | |
| 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 … | |
| 69 (check AIPlayer.h) | |
| 70 C: One-letter code to identify the message type (check message.… | |
| 71 Data: Message-dependent information | |
| 72 | |
| 73 | |
| 74 For compatibility with old clients and servers, the old message struc… | |
| 75 is still supported:- | |
| 76 From^To^ACData | |
| 77 | |
| 78 From,To: Player names identifying the sender and intended recipient … | |
| 79 the message. Either field may be blank, although the server… | |
| 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 … | |
| 85 C is C_PRINTMESSAGE), which simply instructs the client to display "D… | |
| 86 Any ^ characters within Data are replaced by newlines on output. So i… | |
| 87 for the server to instruct player "Fred" (ID 1) to display "Hello wor… | |
| 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 (F… | |
| 95 is not specified in the new format; the player is identified by the s… | |
| 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 … | |
| 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 da… | |
| 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 resulti… | |
| 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 … | |
| 326 * networking is not active (the local server should support all abiliti… | |
| 327 * that the client uses). Call this function prior to calling SendAbilit… | |
| 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 abili… | |
| 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, ErrorRea… | |
| 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 **list… | |
| 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 … | |
| 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->da… | |
| 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.… | |
| 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 link… | |
| 990 * list of known players in "First", sets the other arguments to the mes… | |
| 991 * fields. Data is returned as a pointer into the message "Msg", and sho… | |
| 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 f… | |
| 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 Cod… | |
| 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.BitchK… | |
| 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, ma… | |
| 1345 DefendName, Armament); | |
| 1346 } else { | |
| 1347 dpg_string_append_printf(text, | |
| 1348 _("%s and %d %tde - %s - are chasing you, … | |
| 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"), AttackNa… | |
| 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!"), … | |
| 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 %t… | |
| 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!"), L… | |
| 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 } |