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