tdopewars.c - vaccinewars - be a doctor and try to vaccinate the world
git clone git://src.adamsgaard.dk/vaccinewars
Log
Files
Refs
README
LICENSE
---
tdopewars.c (101007B)
---
     1 /************************************************************************
     2  * dopewars.c     dopewars - general purpose routines and init          *
     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 #define _GNU_SOURCE
    24 
    25 #ifdef HAVE_CONFIG_H
    26 #include 
    27 #endif
    28 
    29 #include 
    30 #include 
    31 #include 
    32 #include 
    33 #ifdef HAVE_UNISTD_H
    34 #include 
    35 #endif
    36 #ifdef HAVE_GETOPT_LONG
    37 #include 
    38 #endif
    39 #include 
    40 #include 
    41 #include 
    42 #include 
    43 
    44 #include "configfile.h"
    45 #include "convert.h"
    46 #include "dopewars.h"
    47 #include "admin.h"
    48 #include "log.h"
    49 #include "message.h"
    50 #include "nls.h"
    51 #include "serverside.h"
    52 #include "sound.h"
    53 #include "tstring.h"
    54 #include "AIPlayer.h"
    55 #include "util.h"
    56 #include "winmain.h"
    57 
    58 #ifdef CURSES_CLIENT
    59 #include "curses_client/curses_client.h"
    60 #endif
    61 
    62 #ifdef GUI_CLIENT
    63 #include "gui_client/gtk_client.h"
    64 #endif
    65 
    66 #ifdef GUI_SERVER
    67 #include "gtkport/gtkport.h"
    68 #endif
    69 
    70 int ClientSock, ListenSock;
    71 gboolean Network, Client, Server, WantAntique = FALSE, UseSounds = TRUE;
    72 
    73 /* 
    74  * dopewars acting as standalone TCP server:
    75  *           Network=Server=TRUE   Client=FALSE
    76  * dopewars acting as client, connecting to standalone server:
    77  *           Network=Client=TRUE   Server=FALSE
    78  * dopewars in single-player or antique mode:
    79  *           Network=Server=Client=FALSE
    80  */
    81 int Port = 7902;
    82 gboolean Sanitized, ConfigVerbose, DrugValue, Antique = FALSE;
    83 gchar *HiScoreFile = NULL, *ServerName = NULL;
    84 gchar *ServerMOTD = NULL, *BindAddress = NULL, *PlayerName = NULL;
    85 
    86 struct DATE StartDate = {
    87   1, 12, 1984
    88 };
    89 
    90 #ifdef CYGWIN
    91 gboolean MinToSysTray = TRUE;
    92 #else
    93 gboolean Daemonize = TRUE;
    94 #endif
    95 
    96 #ifdef CYGWIN
    97 #define SNDPATH "sounds\\19.5degs\\"
    98 #else
    99 #define SNDPATH DPDATADIR"/dopewars/"
   100 #endif
   101 
   102 gchar *OurWebBrowser = NULL;
   103 gint ConfigErrors = 0;
   104 gboolean LocaleIsUTF8 = FALSE;
   105 
   106 int NumLocation = 0, NumGun = 0, NumCop = 0, NumDrug = 0, NumSubway = 0;
   107 int NumPlaying = 0, NumStoppedTo = 0;
   108 int DebtInterest = 10, BankInterest = 5;
   109 Player Noone;
   110 int LoanSharkLoc, BankLoc, GunShopLoc, RoughPubLoc;
   111 int DrugSortMethod = DS_ATOZ;
   112 int FightTimeout = 5, IdleTimeout = 14400, ConnectTimeout = 300;
   113 int MaxClients = 20, AITurnPause = 5;
   114 price_t StartCash = 2000, StartDebt = 5500;
   115 GSList *ServerList = NULL;
   116 
   117 GScannerConfig ScannerConfig = {
   118   " \t\n",                      /* Ignore these characters */
   119 
   120   /* Valid characters for starting an identifier */
   121   G_CSET_a_2_z "_" G_CSET_A_2_Z,
   122 
   123   /* Valid characters for continuing an identifier */
   124   G_CSET_a_2_z "._-0123456789" G_CSET_A_2_Z,
   125 
   126   "#\n",                        /* Single line comments start with # and
   127                                  * end with \n */
   128   FALSE,                        /* Are symbols case sensitive? */
   129   TRUE,                         /* Ignore C-style comments? */
   130   TRUE,                         /* Ignore single-line comments? */
   131   TRUE,                         /* Treat C-style comments as single tokens 
   132                                  * - do not break into words? */
   133   TRUE,                         /* Read identifiers as tokens? */
   134   TRUE,                         /* Read single-character identifiers as
   135                                  * 1-character strings? */
   136   TRUE,                         /* Allow the parsing of NULL as the
   137                                  * G_TOKEN_IDENTIFIER_NULL ? */
   138   FALSE,                        /* Allow symbols (defined by
   139                                  * g_scanner_scope_add_symbol) ? */
   140   TRUE,                         /* Allow binary numbers in 0b1110 format ? */
   141   TRUE,                         /* Allow octal numbers in C-style e.g. 034 ? */
   142   FALSE,                        /* Allow floats? */
   143   TRUE,                         /* Allow hex numbers in C-style e.g. 0xFF ? */
   144   TRUE,                         /* Allow hex numbers in $FF format ? */
   145   TRUE,                         /* Allow '' strings (no escaping) ? */
   146   TRUE,                         /* Allow "" strings (\ escapes parsed) ? */
   147   TRUE,                         /* Convert octal, binary and hex to int? */
   148   FALSE,                        /* Convert ints to floats? */
   149   FALSE,                        /* Treat all identifiers as strings? */
   150   TRUE,                         /* Leave single characters (e.g. {,=)
   151                                  * unchanged, instead of returning
   152                                  * G_TOKEN_CHAR ? */
   153   FALSE,                        /* Replace read symbols with the token
   154                                  * given by their value, instead of
   155                                  * G_TOKEN_SYMBOL ? */
   156   FALSE                         /* scope_0_fallback... */
   157 };
   158 
   159 struct LOCATION StaticLocation, *Location = NULL;
   160 struct DRUG StaticDrug, *Drug = NULL;
   161 struct GUN StaticGun, *Gun = NULL;
   162 struct COP StaticCop, *Cop = NULL;
   163 struct NAMES Names = {
   164   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
   165 };
   166 struct SOUNDS Sounds = {
   167   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   168   NULL, NULL, NULL, NULL, NULL, NULL, NULL
   169 };
   170 
   171 /* N.B. The slightly over-enthusiastic comments here are for the benefit
   172  * of translators ;) */
   173 struct NAMES DefaultNames = {
   174   /* Name of a single bitch - if you need to use different words for
   175      "bitch" depending on where in the sentence it occurs (e.g. subject or
   176      object) then read doc/i18n.html about the %tde (etc.) notation. N.B.
   177      This notation can be used for most of the translatable strings in
   178      dopewars. */
   179   N_("nurse"),
   180   /* Word used for two or more bitches */
   181   N_("nurses"),
   182   /* Word used for a single gun */
   183   N_("syringe"),
   184   /* Word used for two or more guns */
   185   N_("syringes"),
   186   /* Word used for a single drug */
   187   N_("vaccine"),
   188   /* Word used for two or more drugs */
   189   N_("vaccines"),
   190   /* String for displaying the game date or turn number. This is passed
   191      to the strftime() function, with the exception that %T is used to
   192      mean the turn number rather than the calendar date. */
   193   N_("%m-%d-%Y"),
   194   /* Names of the loan shark, the bank, the gun shop, and the pub,
   195      respectively */
   196   N_("the Bank"), N_("the EU"),
   197   N_("the medical supply store"), N_("the University")
   198 };
   199 
   200 struct CURRENCY Currency = {
   201   NULL, TRUE
   202 };
   203 
   204 struct PRICES Prices = {
   205   20000, 10000
   206 };
   207 
   208 struct BITCH Bitch = {
   209   50000, 150000
   210 };
   211 
   212 #ifdef NETWORKING
   213 struct METASERVER MetaServer = {
   214   FALSE, NULL, NULL, NULL, NULL
   215 };
   216 
   217 struct METASERVER DefaultMetaServer = {
   218   TRUE, "https://dopewars.sourceforge.io/metaserver.php", "",
   219   "", "dopewars server"
   220 };
   221 
   222 SocksServer Socks = { NULL, 0, 0, FALSE, NULL, NULL, NULL };
   223 gboolean UseSocks;
   224 #endif
   225 
   226 int NumTurns = 31;
   227 
   228 int PlayerArmor = 100, BitchArmor = 50;
   229 
   230 struct LOG Log;
   231 
   232 struct GLOBALS Globals[] = {
   233   /* The following strings are the helptexts for all the options that can
   234      be set in a dopewars configuration file, or in the server. See
   235      doc/configfile.html for more detailed explanations. */
   236   {&Port, NULL, NULL, NULL, NULL, "Port", N_("Network port to connect to"),
   237    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 65535},
   238   {NULL, NULL, NULL, &HiScoreFile, NULL, "HiScoreFile",
   239    N_("Name of the high score file"), NULL, NULL, 0, "", NULL, NULL, FALSE,
   240    0, 0},
   241   {NULL, NULL, NULL, &ServerName, NULL, "Server",
   242    N_("Name of the server to connect to"), NULL, NULL, 0, "", NULL,
   243    NULL, FALSE, 0, 0},
   244   {NULL, NULL, NULL, &ServerMOTD, NULL, "ServerMOTD",
   245    N_("Server's welcome message of the day"), NULL, NULL, 0, "", NULL,
   246    NULL, FALSE, 0, 0},
   247   {NULL, NULL, NULL, &BindAddress, NULL, "BindAddress",
   248    N_("Network address for the server to listen on"), NULL, NULL, 0, "",
   249    NULL, NULL, FALSE, 0, 0},
   250 #ifdef NETWORKING
   251   {NULL, &UseSocks, NULL, NULL, NULL, "Socks.Active",
   252    N_("TRUE if a SOCKS server should be used for networking"),
   253    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   254 #ifndef CYGWIN
   255   {NULL, &Socks.numuid, NULL, NULL, NULL, "Socks.NumUID",
   256    N_("TRUE if numeric user IDs should be used for SOCKS4"),
   257    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   258 #endif
   259   {NULL, NULL, NULL, &Socks.user, NULL, "Socks.User",
   260    N_("If not blank, the username to use for SOCKS4"),
   261    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   262   {NULL, NULL, NULL, &Socks.name, NULL, "Socks.Name",
   263    N_("The hostname of a SOCKS server to use"),
   264    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   265   {&Socks.port, NULL, NULL, NULL, NULL, "Socks.Port",
   266    N_("The port number of a SOCKS server to use"),
   267    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 65535},
   268   {&Socks.version, NULL, NULL, NULL, NULL, "Socks.Version",
   269    N_("The version of the SOCKS protocol to use (4 or 5)"),
   270    NULL, NULL, 0, "", NULL, NULL, FALSE, 4, 5},
   271   {NULL, NULL, NULL, &Socks.authuser, NULL, "Socks.Auth.User",
   272    N_("Username for SOCKS5 authentication"),
   273    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   274   {NULL, NULL, NULL, &Socks.authpassword, NULL, "Socks.Auth.Password",
   275    N_("Password for SOCKS5 authentication"),
   276    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   277   {NULL, &MetaServer.Active, NULL, NULL, NULL, "MetaServer.Active",
   278    N_("TRUE if server should report to a metaserver"),
   279    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   280   {NULL, NULL, NULL, &MetaServer.URL, NULL, "MetaServer.URL",
   281    N_("Metaserver URL to report/get server details to/from"),
   282    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   283   {NULL, NULL, NULL, &MetaServer.LocalName, NULL, "MetaServer.LocalName",
   284    N_("Preferred hostname of your server machine"), NULL, NULL, 0, "",
   285    NULL, NULL, FALSE, 0, 0},
   286   {NULL, NULL, NULL, &MetaServer.Password, NULL, "MetaServer.Password",
   287    N_("Authentication for LocalName with the metaserver"), NULL, NULL, 0,
   288    "", NULL, NULL, FALSE, 0, 0},
   289   {NULL, NULL, NULL, &MetaServer.Comment, NULL, "MetaServer.Comment",
   290    N_("Server description, reported to the metaserver"), NULL, NULL, 0, "",
   291    NULL, NULL, FALSE, 0, 0},
   292 #endif /* NETWORKING */
   293 #ifdef CYGWIN
   294   {NULL, &MinToSysTray, NULL, NULL, NULL, "MinToSysTray",
   295    N_("If TRUE, the server minimizes to the System Tray"),
   296    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   297 #else
   298   {NULL, &Daemonize, NULL, NULL, NULL, "Daemonize",
   299    N_("If TRUE, the server runs in the background"),
   300    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   301   {NULL, NULL, NULL, &OurWebBrowser, NULL, "WebBrowser",
   302    N_("The command used to start your web browser"),
   303    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   304 #endif
   305   {&NumTurns, NULL, NULL, NULL, NULL, "NumTurns",
   306    N_("No. of game turns (if 0, game never ends)"),
   307    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   308   {&StartDate.day, NULL, NULL, NULL, NULL, "StartDate.Day",
   309    N_("Day of the month on which the game starts"),
   310    NULL, NULL, 0, "", NULL, NULL, FALSE, 1, 31},
   311   {&StartDate.month, NULL, NULL, NULL, NULL, "StartDate.Month",
   312    N_("Month in which the game starts"),
   313    NULL, NULL, 0, "", NULL, NULL, FALSE, 1, 12},
   314   {&StartDate.year, NULL, NULL, NULL, NULL, "StartDate.Year",
   315    N_("Year in which the game starts"),
   316    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   317   {NULL, NULL, NULL, &Currency.Symbol, NULL, "Currency.Symbol",
   318    N_("The currency symbol (e.g. $)"),
   319    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   320   {NULL, &Currency.Prefix, NULL, NULL, NULL, "Currency.Prefix",
   321    N_("If TRUE, the currency symbol precedes prices"),
   322    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   323   {NULL, NULL, NULL, &Log.File, NULL, "Log.File",
   324    N_("File to write log messages to"),
   325    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   326   {&Log.Level, NULL, NULL, NULL, NULL, "Log.Level",
   327    N_("Controls the number of log messages produced"),
   328    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 5},
   329   {NULL, NULL, NULL, &Log.Timestamp, NULL, "Log.Timestamp",
   330    N_("strftime() format string for log timestamps"),
   331    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   332   {NULL, &Sanitized, NULL, NULL, NULL, "Sanitized",
   333    N_("Random events are sanitized"), NULL, NULL, 0, "", NULL, NULL, FALSE,
   334    0, 0},
   335   {NULL, &DrugValue, NULL, NULL, NULL, "DrugValue",
   336    N_("TRUE if the value of bought drugs should be saved"),
   337    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   338   {NULL, &ConfigVerbose, NULL, NULL, NULL, "ConfigVerbose",
   339    N_("Be verbose in processing config file"), NULL, NULL, 0, "", NULL,
   340    NULL, FALSE, 0, 0},
   341   {&NumLocation, NULL, NULL, NULL, NULL, "NumLocation",
   342    N_("Number of locations in the game"),
   343    (void **)(&Location), NULL, sizeof(struct LOCATION), "", NULL,
   344    ResizeLocations, FALSE, 1, -1},
   345   {&NumCop, NULL, NULL, NULL, NULL, "NumCop",
   346    N_("Number of types of cop in the game"),
   347    (void **)(&Cop), NULL, sizeof(struct COP), "", NULL, ResizeCops, FALSE,
   348    0, -1},
   349   {&NumGun, NULL, NULL, NULL, NULL, "NumGun",
   350    N_("Number of guns in the game"),
   351    (void **)(&Gun), NULL, sizeof(struct GUN), "", NULL, ResizeGuns, FALSE,
   352    0, -1},
   353   {&NumDrug, NULL, NULL, NULL, NULL, "NumDrug",
   354    N_("Number of drugs in the game"),
   355    (void **)(&Drug), NULL, sizeof(struct DRUG), "", NULL, ResizeDrugs,
   356    FALSE, 1, -1},
   357   {&LoanSharkLoc, NULL, NULL, NULL, NULL, "LoanShark",
   358    N_("Location of the Loan Shark"), NULL, NULL, 0, "", NULL, NULL, FALSE,
   359    0, 0},
   360   {&BankLoc, NULL, NULL, NULL, NULL, "Bank", N_("Location of the bank"),
   361    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   362   {&GunShopLoc, NULL, NULL, NULL, NULL, "GunShop",
   363    N_("Location of the gun shop"),
   364    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   365   {&RoughPubLoc, NULL, NULL, NULL, NULL, "RoughPub",
   366    N_("Location of the pub"),
   367    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   368   {&DebtInterest, NULL, NULL, NULL, NULL, "DebtInterest",
   369    N_("Daily interest rate on the loan shark debt"),
   370    NULL, NULL, 0, "", NULL, NULL, FALSE, -100, -200},
   371   {&BankInterest, NULL, NULL, NULL, NULL, "BankInterest",
   372    N_("Daily interest rate on your bank balance"),
   373    NULL, NULL, 0, "", NULL, NULL, FALSE, -100, -200},
   374   {NULL, NULL, NULL, &Names.LoanSharkName, NULL, "LoanSharkName",
   375    N_("Name of the loan shark"), NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   376   {NULL, NULL, NULL, &Names.BankName, NULL, "BankName",
   377    N_("Name of the bank"), NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   378   {NULL, NULL, NULL, &Names.GunShopName, NULL, "GunShopName",
   379    N_("Name of the gun shop"), NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   380   {NULL, NULL, NULL, &Names.RoughPubName, NULL, "RoughPubName",
   381    N_("Name of the pub"), NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   382   {NULL, &UseSounds, NULL, NULL, NULL, "UseSounds",
   383    N_("TRUE if sounds should be enabled"),
   384    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   385   {NULL, NULL, NULL, &Sounds.FightHit, NULL, "Sounds.FightHit",
   386    N_("Sound file played for a gun \"hit\""), NULL, NULL, 0, "",
   387    NULL, NULL, FALSE, 0, 0},
   388   {NULL, NULL, NULL, &Sounds.FightMiss, NULL, "Sounds.FightMiss",
   389    N_("Sound file played for a gun \"miss\""), NULL, NULL, 0, "",
   390    NULL, NULL, FALSE, 0, 0},
   391   {NULL, NULL, NULL, &Sounds.FightReload, NULL, "Sounds.FightReload",
   392    N_("Sound file played when guns are reloaded"), NULL, NULL, 0, "",
   393    NULL, NULL, FALSE, 0, 0},
   394   {NULL, NULL, NULL, &Sounds.EnemyBitchKilled, NULL, "Sounds.EnemyBitchKilled",
   395    N_("Sound file played when an enemy bitch/deputy is killed"),
   396    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   397   {NULL, NULL, NULL, &Sounds.BitchKilled, NULL, "Sounds.BitchKilled",
   398    N_("Sound file played when one of your bitches is killed"),
   399    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   400   {NULL, NULL, NULL, &Sounds.EnemyKilled, NULL, "Sounds.EnemyKilled",
   401    N_("Sound file played when another player or cop is killed"),
   402    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   403   {NULL, NULL, NULL, &Sounds.Killed, NULL, "Sounds.Killed",
   404    N_("Sound file played when you are killed"),
   405    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   406   {NULL, NULL, NULL, &Sounds.EnemyFailFlee, NULL, "Sounds.EnemyFailFlee",
   407    N_("Sound file played when a player tries to escape, but fails"),
   408    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   409   {NULL, NULL, NULL, &Sounds.FailFlee, NULL, "Sounds.FailFlee",
   410    N_("Sound file played when you try to escape, but fail"),
   411    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   412   {NULL, NULL, NULL, &Sounds.EnemyFlee, NULL, "Sounds.EnemyFlee",
   413    N_("Sound file played when a player successfully escapes"),
   414    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   415   {NULL, NULL, NULL, &Sounds.Flee, NULL, "Sounds.Flee",
   416    N_("Sound file played when you successfully escape"),
   417    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   418   {NULL, NULL, NULL, &Sounds.Jet, NULL, "Sounds.Jet",
   419    N_("Sound file played on arriving at a new location"), NULL, NULL, 0, "",
   420    NULL, NULL, FALSE, 0, 0},
   421   {NULL, NULL, NULL, &Sounds.TalkToAll, NULL, "Sounds.TalkToAll",
   422    N_("Sound file played when a player sends a public chat message"),
   423    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   424   {NULL, NULL, NULL, &Sounds.TalkPrivate, NULL, "Sounds.TalkPrivate",
   425    N_("Sound file played when a player sends a private chat message"),
   426    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   427   {NULL, NULL, NULL, &Sounds.JoinGame, NULL, "Sounds.JoinGame",
   428    N_("Sound file played when a player joins the game"),
   429    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   430   {NULL, NULL, NULL, &Sounds.LeaveGame, NULL, "Sounds.LeaveGame",
   431    N_("Sound file played when a player leaves the game"),
   432    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   433   {NULL, NULL, NULL, &Sounds.StartGame, NULL, "Sounds.StartGame",
   434    N_("Sound file played at the start of the game"),
   435    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   436   {NULL, NULL, NULL, &Sounds.EndGame, NULL, "Sounds.EndGame",
   437    N_("Sound file played at the end of the game"),
   438    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   439   {&DrugSortMethod, NULL, NULL, NULL, NULL, "DrugSortMethod",
   440    N_("Sort key for listing available drugs"),
   441    NULL, NULL, 0, "", NULL, NULL, FALSE, 1, 4},
   442   {&FightTimeout, NULL, NULL, NULL, NULL, "FightTimeout",
   443    N_("No. of seconds in which to return fire"),
   444    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   445   {&IdleTimeout, NULL, NULL, NULL, NULL, "IdleTimeout",
   446    N_("Players are disconnected after this many seconds"),
   447    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   448   {&ConnectTimeout, NULL, NULL, NULL, NULL, "ConnectTimeout",
   449    N_("Time in seconds for connections to be made or broken"),
   450    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   451   {&MaxClients, NULL, NULL, NULL, NULL, "MaxClients",
   452    N_("Maximum number of TCP/IP connections"),
   453    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   454   {&AITurnPause, NULL, NULL, NULL, NULL, "AITurnPause",
   455    N_("Seconds between turns of AI players"),
   456    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   457   {NULL, NULL, &StartCash, NULL, NULL, "StartCash",
   458    N_("Amount of cash that each player starts with"),
   459    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   460   {NULL, NULL, &StartDebt, NULL, NULL, "StartDebt",
   461    N_("Amount of debt that each player starts with"),
   462    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   463   {NULL, NULL, NULL, &StaticLocation.Name, NULL, "Name",
   464    N_("Name of each location"), (void **)(&Location), &StaticLocation,
   465    sizeof(struct LOCATION), "Location", &NumLocation, NULL, FALSE, 0, -1},
   466   {&(StaticLocation.PolicePresence), NULL, NULL, NULL, NULL,
   467    "PolicePresence",
   468    N_("Police presence at each location (%)"),
   469    (void **)(&Location), &StaticLocation,
   470    sizeof(struct LOCATION), "Location", &NumLocation, NULL, FALSE, 0, 100},
   471   {&(StaticLocation.MinDrug), NULL, NULL, NULL, NULL, "MinDrug",
   472    N_("Minimum number of drugs at each location"),
   473    (void **)(&Location), &StaticLocation,
   474    sizeof(struct LOCATION), "Location", &NumLocation, NULL, FALSE, 1, -1},
   475   {&(StaticLocation.MaxDrug), NULL, NULL, NULL, NULL, "MaxDrug",
   476    N_("Maximum number of drugs at each location"),
   477    (void **)(&Location), &StaticLocation,
   478    sizeof(struct LOCATION), "Location", &NumLocation, NULL, FALSE, 1, -1},
   479   {&PlayerArmor, NULL, NULL, NULL, NULL, "PlayerArmour",
   480    N_("% resistance to gunshots of each player"),
   481    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 100},
   482   {&PlayerArmor, NULL, NULL, NULL, NULL, "PlayerArmor",
   483    N_("% resistance to gunshots of each player"),
   484    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 100},
   485   {&BitchArmor, NULL, NULL, NULL, NULL, "BitchArmour",
   486    N_("% resistance to gunshots of each bitch"),
   487    NULL, NULL, 0, "", NULL, NULL, FALSE, 1, 100},
   488   {&BitchArmor, NULL, NULL, NULL, NULL, "BitchArmor",
   489    N_("% resistance to gunshots of each bitch"),
   490    NULL, NULL, 0, "", NULL, NULL, FALSE, 1, 100},
   491   {NULL, NULL, NULL, &StaticCop.Name, NULL, "Name",
   492    N_("Name of each cop"),
   493    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   494    NULL, FALSE, 0, 0},
   495   {NULL, NULL, NULL, &StaticCop.DeputyName, NULL, "DeputyName",
   496    N_("Name of each cop's deputy"),
   497    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   498    NULL, FALSE, 0, 0},
   499   {NULL, NULL, NULL, &StaticCop.DeputiesName, NULL, "DeputiesName",
   500    N_("Name of each cop's deputies"),
   501    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   502    NULL, FALSE, 0, 0},
   503   {&StaticCop.Armor, NULL, NULL, NULL, NULL, "Armour",
   504    N_("% resistance to gunshots of each cop"),
   505    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   506    NULL, FALSE, 1, 100},
   507   {&StaticCop.Armor, NULL, NULL, NULL, NULL, "Armor",
   508    N_("% resistance to gunshots of each cop"),
   509    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   510    NULL, FALSE, 1, 100},
   511   {&StaticCop.DeputyArmor, NULL, NULL, NULL, NULL, "DeputyArmour",
   512    N_("% resistance to gunshots of each deputy"),
   513    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   514    NULL, FALSE, 1, 100},
   515   {&StaticCop.DeputyArmor, NULL, NULL, NULL, NULL, "DeputyArmor",
   516    N_("% resistance to gunshots of each deputy"),
   517    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   518    NULL, FALSE, 1, 100},
   519   {&StaticCop.AttackPenalty, NULL, NULL, NULL, NULL, "AttackPenalty",
   520    N_("Attack penalty relative to a player"),
   521    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   522    NULL, FALSE, 0, 100},
   523   {&StaticCop.DefendPenalty, NULL, NULL, NULL, NULL, "DefendPenalty",
   524    N_("Defend penalty relative to a player"),
   525    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   526    NULL, FALSE, 0, 100},
   527   {&StaticCop.MinDeputies, NULL, NULL, NULL, NULL, "MinDeputies",
   528    N_("Minimum number of accompanying deputies"),
   529    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   530    NULL, FALSE, 0, -1},
   531   {&StaticCop.MaxDeputies, NULL, NULL, NULL, NULL, "MaxDeputies",
   532    N_("Maximum number of accompanying deputies"),
   533    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   534    NULL, FALSE, 0, -1},
   535   {&StaticCop.GunIndex, NULL, NULL, NULL, NULL, "GunIndex",
   536    N_("Zero-based index of the gun that cops are armed with"),
   537    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   538    NULL, FALSE, 0, -1},
   539   {&StaticCop.CopGun, NULL, NULL, NULL, NULL, "CopGun",
   540    N_("Number of guns that each cop carries"),
   541    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   542    NULL, FALSE, 0, -1},
   543   {&StaticCop.DeputyGun, NULL, NULL, NULL, NULL, "DeputyGun",
   544    N_("Number of guns that each deputy carries"),
   545    (void **)(&Cop), &StaticCop, sizeof(struct COP), "Cop", &NumCop,
   546    NULL, FALSE, 0, -1},
   547   {NULL, NULL, NULL, &StaticDrug.Name, NULL, "Name",
   548    N_("Name of each drug"),
   549    (void **)(&Drug), &StaticDrug,
   550    sizeof(struct DRUG), "Drug", &NumDrug, NULL, FALSE, 0, 0},
   551   {NULL, NULL, &(StaticDrug.MinPrice), NULL, NULL, "MinPrice",
   552    N_("Minimum normal price of each drug"),
   553    (void **)(&Drug), &StaticDrug,
   554    sizeof(struct DRUG), "Drug", &NumDrug, NULL, FALSE, 1, -1},
   555   {NULL, NULL, &(StaticDrug.MaxPrice), NULL, NULL, "MaxPrice",
   556    N_("Maximum normal price of each drug"),
   557    (void **)(&Drug), &StaticDrug,
   558    sizeof(struct DRUG), "Drug", &NumDrug, NULL, FALSE, 1, -1},
   559   {NULL, &(StaticDrug.Cheap), NULL, NULL, NULL, "Cheap",
   560    N_("TRUE if this drug can be specially cheap"),
   561    (void **)(&Drug), &StaticDrug,
   562    sizeof(struct DRUG), "Drug", &NumDrug, NULL, FALSE, 0, 0},
   563   {NULL, &(StaticDrug.Expensive), NULL, NULL, NULL, "Expensive",
   564    N_("TRUE if this drug can be specially expensive"),
   565    (void **)(&Drug), &StaticDrug,
   566    sizeof(struct DRUG), "Drug", &NumDrug, NULL, FALSE, 0, 0},
   567   {NULL, NULL, NULL, &StaticDrug.CheapStr, NULL, "CheapStr",
   568    N_("Message displayed when this drug is specially cheap"),
   569    (void **)(&Drug), &StaticDrug,
   570    sizeof(struct DRUG), "Drug", &NumDrug, NULL, FALSE, 0, 0},
   571   {NULL, NULL, NULL, &Drugs.ExpensiveStr1, NULL, "Drugs.ExpensiveStr1",
   572    N_("Format string used for expensive drugs 50% of time"),
   573    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   574   {NULL, NULL, NULL, &Drugs.ExpensiveStr2, NULL, "Drugs.ExpensiveStr2",
   575    /* xgettext:no-c-format */
   576    N_("Format string used for expensive drugs 50% of time"),
   577    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   578   {&(Drugs.CheapDivide), NULL, NULL, NULL, NULL, "Drugs.CheapDivide",
   579    N_("Divider for drug price when it's specially cheap"),
   580    NULL, NULL, 0, "", NULL, NULL, FALSE, 1, -1},
   581   {&(Drugs.ExpensiveMultiply), NULL, NULL, NULL, NULL,
   582    "Drugs.ExpensiveMultiply",
   583    N_("Multiplier for specially expensive drug prices"),
   584    NULL, NULL, 0, "", NULL, NULL, FALSE, 1, -1},
   585   {NULL, NULL, NULL, &StaticGun.Name, NULL, "Name",
   586    N_("Name of each gun"),
   587    (void **)(&Gun), &StaticGun,
   588    sizeof(struct GUN), "Gun", &NumGun, NULL, FALSE, 0, 0},
   589   {NULL, NULL, &(StaticGun.Price), NULL, NULL, "Price",
   590    N_("Price of each gun"),
   591    (void **)(&Gun), &StaticGun,
   592    sizeof(struct GUN), "Gun", &NumGun, NULL, FALSE, 0, 0},
   593   {&(StaticGun.Space), NULL, NULL, NULL, NULL, "Space",
   594    N_("Space taken by each syringe"),
   595    (void **)(&Gun), &StaticGun,
   596    sizeof(struct GUN), "Gun", &NumGun, NULL, FALSE, 0, -1},
   597   {&(StaticGun.Damage), NULL, NULL, NULL, NULL, "Damage",
   598    N_("Damage done by each gun"),
   599    (void **)(&Gun), &StaticGun,
   600    sizeof(struct GUN), "Gun", &NumGun, NULL, FALSE, 0, -1},
   601   {NULL, NULL, NULL, &Names.Bitch, NULL, "Names.Bitch",
   602    N_("Word used to denote a single \"bitch\""), NULL, NULL, 0, "", NULL,
   603    NULL, FALSE, 0, 0},
   604   {NULL, NULL, NULL, &Names.Bitches, NULL, "Names.Bitches",
   605    N_("Word used to denote two or more \"bitches\""),
   606    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   607   {NULL, NULL, NULL, &Names.Gun, NULL, "Names.Gun",
   608    N_("Word used to denote a single gun or equivalent"), NULL, NULL, 0, "",
   609    NULL, NULL, FALSE, 0, 0},
   610   {NULL, NULL, NULL, &Names.Guns, NULL, "Names.Guns",
   611    N_("Word used to denote two or more guns"), NULL, NULL, 0, "", NULL,
   612    NULL, FALSE, 0, 0},
   613   {NULL, NULL, NULL, &Names.Drug, NULL, "Names.Drug",
   614    N_("Word used to denote a single drug or equivalent"), NULL, NULL, 0,
   615    "", NULL, NULL, FALSE, 0, 0},
   616   {NULL, NULL, NULL, &Names.Drugs, NULL, "Names.Drugs",
   617    N_("Word used to denote two or more drugs"), NULL, NULL, 0, "", NULL,
   618    NULL, FALSE, 0, 0},
   619   {NULL, NULL, NULL, &Names.Date, NULL, "Names.Date",
   620    N_("strftime() format string for displaying the game turn"),
   621    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, 0},
   622   {NULL, NULL, &Prices.Spy, NULL, NULL, "Prices.Spy",
   623    N_("Cost for a bitch to spy on the enemy"),
   624    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   625   {NULL, NULL, &Prices.Tipoff, NULL, NULL, "Prices.Tipoff",
   626    N_("Cost for a bitch to tipoff the cops to an enemy"),
   627    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   628   {NULL, NULL, &Bitch.MinPrice, NULL, NULL, "Bitch.MinPrice",
   629    N_("Minimum price to hire a bitch"),
   630    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   631   {NULL, NULL, &Bitch.MaxPrice, NULL, NULL, "Bitch.MaxPrice",
   632    N_("Maximum price to hire a bitch"),
   633    NULL, NULL, 0, "", NULL, NULL, FALSE, 0, -1},
   634   {NULL, NULL, NULL, NULL, &SubwaySaying, "SubwaySaying",
   635    N_("List of things which you overhear on the internet"),
   636    NULL, NULL, 0, "", &NumSubway, ResizeSubway, FALSE, 0, 0},
   637   {&NumSubway, NULL, NULL, NULL, NULL, "NumSubwaySaying",
   638    N_("Number of subway sayings"),
   639    NULL, NULL, 0, "", NULL, ResizeSubway, FALSE, 0, -1},
   640   {NULL, NULL, NULL, NULL, &Playing, "Playing",
   641    N_("List of songs which you can hear playing"),
   642    NULL, NULL, 0, "", &NumPlaying, ResizePlaying, FALSE, 0, 0},
   643   {&NumPlaying, NULL, NULL, NULL, NULL, "NumPlaying",
   644    N_("Number of playing songs"),
   645    NULL, NULL, 0, "", NULL, ResizePlaying, FALSE, 0, -1},
   646   {NULL, NULL, NULL, NULL, &StoppedTo, "StoppedTo",
   647    N_("List of things which you can stop to do"),
   648    NULL, NULL, 0, "", &NumStoppedTo, ResizeStoppedTo, FALSE, 0, 0},
   649   {&NumStoppedTo, NULL, NULL, NULL, NULL, "NumStoppedTo",
   650    N_("Number of things which you can stop to do"),
   651    NULL, NULL, 0, "", NULL, ResizeStoppedTo, FALSE, 0, -1}
   652 };
   653 const int NUMGLOB = sizeof(Globals) / sizeof(Globals[0]);
   654 
   655 char **Playing = NULL;
   656 char *DefaultPlaying[] = {
   657   /* Default list of songs that you can hear playing (N.B. this can be
   658      overridden in the configuration file with the "Playing" variable) -
   659      look for "You hear someone playing %s" to see how these are used. */
   660   N_("`Are you Experienced` by Jimi Hendrix"),
   661   N_("`Cheeba Cheeba` by Tone Loc"),
   662   N_("`Comin` in to Los Angeles` by Arlo Guthrie"),
   663   N_("`Commercial` by Spanky and Our Gang"),
   664   N_("`Late in the Evening` by Paul Simon"),
   665   N_("`Light Up` by Styx"),
   666   N_("`Mexico` by Jefferson Airplane"),
   667   N_("`One toke over the line` by Brewer & Shipley"),
   668   N_("`The Smokeout` by Shel Silverstein"),
   669   N_("`White Rabbit` by Jefferson Airplane"),
   670   N_("`Itchycoo Park` by Small Faces"),
   671   N_("`White Punks on Dope` by the Tubes"),
   672   N_("`Legend of a Mind` by the Moody Blues"),
   673   N_("`Eight Miles High` by the Byrds"),
   674   N_("`Acapulco Gold` by Riders of the Purple Sage"),
   675   N_("`Kicks` by Paul Revere & the Raiders"),
   676   N_("the Nixon tapes"),
   677   N_("`Legalize It` by Mojo Nixon & Skid Roper")
   678 };
   679 
   680 char **StoppedTo = NULL;
   681 char *DefaultStoppedTo[] = {
   682   /* Default list of things which you can "stop to do" (random events that
   683      cost you a little money). These can be overridden with the "StoppedTo"
   684      variable in the configuration file. See the later string "You stopped
   685      to %s." to see how these strings are used. */
   686   N_("have a beer"),
   687   N_("COVID19 test a child"),
   688   N_("check prostata"),
   689   N_("start revolution"),
   690   N_("yell at politicians")
   691 };
   692 
   693 struct COP DefaultCop[] = {
   694   /* Name of the first police officer to attack you */
   695   {N_("An antivaxxer"),
   696    /* Name of a single deputy of the first police officer */
   697    N_("facebook follower"),
   698    /* Word used for more than one deputy of the first police officer */
   699    N_("facebook followers"), 4, 3, 30, 30, 2, 8, 0, 1, 1},
   700   /* Ditto, for the other police officers */
   701   {N_("Bob"), N_("antivaxxer"), N_("antivaxxers"), 15, 4, 30, 20, 4, 10,
   702    0, 2, 1},
   703   {N_("Fietsopa"), N_("antivaxxer"), N_("antivaxxers"), 50, 6, 20, 20, 6, 18, 1, 3, 2}
   704 };
   705 
   706 struct GUN DefaultGun[] = {
   707   /* The names of the default guns */
   708   {N_("Needleless syringe"), 3000, 4, 5},
   709   {N_("Nasal syringe"), 3500, 4, 9},
   710   {N_("Safety syringe"), 2900, 4, 4},
   711   {N_("Luer-Lock syringe"), 3100, 4, 7}
   712 };
   713 
   714 struct DRUG DefaultDrug[] = {
   715   /* The names of the default drugs, and the messages displayed when they
   716      are specially cheap or expensive */
   717   {N_("AstraZenica"), 1000, 4400, TRUE, FALSE,
   718    N_("The market is flooded with cheap home-made AstraZenica!")},
   719   {N_("Pfizer/BioNTech"), 15000, 29000, FALSE, TRUE, ""},
   720   {N_("SputnikLight"), 480, 1280, TRUE, FALSE,
   721    N_("A large batch of Russian Sputnik Light is delivered!")},
   722   {N_("Heroin"), 5500, 13000, FALSE, TRUE, ""},
   723   {N_("Sputnik V"), 11, 60, TRUE, FALSE,
   724    N_("Rival vaccine dealers raided a freezer and are selling cheap Sputnik V!")},
   725   {N_("Sinopharm"), 1500, 4400, FALSE, FALSE, ""},
   726   {N_("Moderna"), 540, 1250, FALSE, TRUE, ""},
   727   {N_("RBD-Dimer"), 1000, 2500, FALSE, FALSE, ""},
   728   {N_("CoviVac"), 220, 700, FALSE, FALSE, ""},
   729   {N_("Covaxin"), 630, 1300, FALSE, FALSE, ""},
   730   {N_("QazCovid-in"), 90, 250, FALSE, TRUE, ""},
   731   {N_("Johns.&Johns."), 315, 890, TRUE, FALSE,
   732    N_("Some countries have cancelled Johnson & Johnson! "
   733       "Prices have bottomed out!")}
   734 };
   735 
   736 #define NUMDRUG (sizeof(DefaultDrug)/sizeof(DefaultDrug[0]))
   737 
   738 struct LOCATION DefaultLocation[] = {
   739   /* The names of the default locations */
   740   {N_("Germany"), 10, NUMDRUG / 2 + 1, NUMDRUG},
   741   {N_("Russia"), 5, NUMDRUG / 2 + 2, NUMDRUG},
   742   {N_("USA"), 15, NUMDRUG / 2, NUMDRUG},
   743   {N_("Myanmar"), 90, NUMDRUG / 2 - 2, NUMDRUG - 2},
   744   {N_("Latvia"), 20, NUMDRUG / 2, NUMDRUG},
   745   {N_("Denmark"), 70, NUMDRUG / 2 - 2, NUMDRUG - 1},
   746   {N_("Trinidad and Tobago"), 50, NUMDRUG / 2, NUMDRUG},
   747   {N_("Uganda"), 20, NUMDRUG / 2, NUMDRUG}
   748 };
   749 
   750 struct DRUGS Drugs = { NULL, NULL, 0, 0 };
   751 struct DRUGS DefaultDrugs = {
   752   /* Messages displayed for drug busts, etc. */
   753   N_("Antivaxxers made a big %tde facebook campaign! Prices are outrageous!"),
   754   N_("Doctors are buying %tde at ridiculous prices!"),
   755   4, 4
   756 };
   757 
   758 char **SubwaySaying = NULL;
   759 char *DefaultSubwaySaying[] = {
   760   /* Default list of things which the "lady on the subway" can tell you
   761      (N.B. can be overridden with the "SubwaySaying" config. file
   762      variable). Look for "the lady next to you" to see how these strings
   763      are used. */
   764   N_("Oh hai Mark"),
   765   N_("Anyway, how is your sex life?"),
   766   N_("I\'ll bet you have some really interesting dreams"),
   767   N_("So I think I\'m going to bitreichcon this year"),
   768   N_("Son, you need a yellow haircut"),
   769   N_("I think it\'s wonderful what they\'re doing with vaccines these days"),
   770   N_("I wasn\'t always a woman, you know"),
   771   N_("I feel a blood clod coming"),
   772   N_("Are you high on something?"),
   773   N_("Oh, you must be from Wales"),
   774   N_("I used to be a hippie, myself"),
   775   N_("There\'s nothing like having lots of money"),
   776   N_("You look like an aardvark!"),
   777   N_("I don\'t believe in Angela Merkel"),
   778   N_("Courage!  Bush is a noodle!"),
   779   N_("Haven\'t I seen you on TV?"),
   780   N_("I think hemorrhoid commercials are really neat!"),
   781   N_("We\'re winning the war for drugs!"),
   782   N_("A day without dope is like night"),
   783   /* xgettext:no-c-format */
   784   N_("We only use 20% of our brains, so why not burn out the other 80%"),
   785   N_("I\'m soliciting contributions for Zombies for Christ"),
   786   N_("I\'d like to sell you an edible poodle"),
   787   N_("Winners don\'t do vaccines... unless they do"),
   788   N_("Kill an antivaxxer for Christ!"),
   789   N_("I am the walrus!"),
   790   N_("Jesus loves you more than you will know"),
   791   N_("I feel an unaccountable urge to dye my hair blue"),
   792   N_("Wasn\'t Jane Fonda wonderful in Barbarella"),
   793   N_("Just say No... well, maybe... ok, what the hell!"),
   794   N_("Would you like a jelly baby?"),
   795   N_("Vaccines can be your friend!")
   796 };
   797 
   798 static gboolean SetConfigValue(int GlobalIndex, int StructIndex,
   799                                gboolean IndexGiven, Converter *conv,
   800                                GScanner *scanner);
   801 /* 
   802  * Returns a random integer not less than bot and less than top.
   803  */
   804 int brandom(int bot, int top)
   805 {
   806   return (int)((float)(top - bot) * rand() / (RAND_MAX + 1.0)) + bot;
   807 }
   808 
   809 /* 
   810  * Returns a random price not less than bot and less than top.
   811  */
   812 price_t prandom(price_t bot, price_t top)
   813 {
   814   return (price_t)((float)(top - bot) * rand() / (RAND_MAX + 1.0)) + bot;
   815 }
   816 
   817 /* 
   818  * Returns the total numbers of players in the list starting at "First";
   819  * players still in the process of connecting or leaving, and those that
   820  * are actually cops (server-created internal AI players) are ignored.
   821  */
   822 int CountPlayers(GSList *First)
   823 {
   824   GSList *list;
   825   Player *Play;
   826   int count = 0;
   827 
   828   for (list = First; list; list = g_slist_next(list)) {
   829     Play = (Player *)list->data;
   830     if (strlen(GetPlayerName(Play)) > 0 && !IsCop(Play))
   831       count++;
   832   }
   833   return count;
   834 }
   835 
   836 /* 
   837  * Adds the new Player structure "NewPlayer" to the linked list
   838  * pointed to by "First", and initializes all fields. Returns the new
   839  * start of the list. If this function is called by the server, then
   840  * it should pass the file descriptor of the socket used to
   841  * communicate with the client player.
   842  */
   843 GSList *AddPlayer(int fd, Player *NewPlayer, GSList *First)
   844 {
   845   Player *tmp;
   846   GSList *list;
   847 
   848   list = First;
   849   NewPlayer->ID = 0;
   850   /* Generate a unique player ID, if we're the server (clients get their
   851    * IDs from the server, so don't need to generate IDs) */
   852   if (Server) {
   853     while (list) {
   854       tmp = (Player *)list->data;
   855       if (tmp->ID == NewPlayer->ID) {
   856         NewPlayer->ID++;
   857         list = First;
   858       } else {
   859         list = g_slist_next(list);
   860       }
   861     }
   862   }
   863   NewPlayer->Name = NULL;
   864   SetPlayerName(NewPlayer, NULL);
   865   NewPlayer->IsAt = 0;
   866   NewPlayer->EventNum = E_NONE;
   867   NewPlayer->FightTimeout = NewPlayer->ConnectTimeout =
   868       NewPlayer->IdleTimeout = 0;
   869   NewPlayer->Guns = (Inventory *)g_malloc0(NumGun * sizeof(Inventory));
   870   NewPlayer->Drugs = (Inventory *)g_malloc0(NumDrug * sizeof(Inventory));
   871   InitList(&(NewPlayer->SpyList));
   872   InitList(&(NewPlayer->TipList));
   873   NewPlayer->Turn = 1;
   874   NewPlayer->date = g_date_new_dmy(StartDate.day, StartDate.month,
   875                                    StartDate.year);
   876   NewPlayer->Cash = StartCash;
   877   NewPlayer->Debt = StartDebt;
   878   NewPlayer->Bank = 0;
   879   NewPlayer->Bitches.Carried = 8;
   880   NewPlayer->CopIndex = 0;
   881   NewPlayer->Health = 100;
   882   NewPlayer->CoatSize = 100;
   883   NewPlayer->Flags = 0;
   884 #ifdef NETWORKING
   885   InitNetworkBuffer(&NewPlayer->NetBuf, '\n', '\r',
   886                     UseSocks ? &Socks : NULL);
   887   if (Server)
   888     BindNetworkBufferToSocket(&NewPlayer->NetBuf, fd);
   889 #endif
   890   InitAbilities(NewPlayer);
   891   NewPlayer->FightArray = NULL;
   892   NewPlayer->Attacking = NULL;
   893   return g_slist_append(First, (gpointer)NewPlayer);
   894 }
   895 
   896 /* 
   897  * Returns TRUE only if the given player has properly connected (i.e. has
   898  * a valid name).
   899  */
   900 gboolean IsConnectedPlayer(Player *play)
   901 {
   902   return (play && play->Name && play->Name[0]);
   903 }
   904 
   905 /* 
   906  * Redimensions the Gun and Drug lists for "Play".
   907  */
   908 void UpdatePlayer(Player *Play)
   909 {
   910   Play->Guns =
   911       (Inventory *)g_realloc(Play->Guns, NumGun * sizeof(Inventory));
   912   Play->Drugs =
   913       (Inventory *)g_realloc(Play->Drugs, NumDrug * sizeof(Inventory));
   914 }
   915 
   916 /* 
   917  * Removes the Player structure pointed to by "Play" from the linked
   918  * list starting at "First". The client socket is freed if called
   919  * from the server. The new start of the list is returned.
   920  */
   921 GSList *RemovePlayer(Player *Play, GSList *First)
   922 {
   923   g_assert(Play);
   924   g_assert(First);
   925 
   926   First = g_slist_remove(First, (gpointer)Play);
   927 #ifdef NETWORKING
   928   if (!IsCop(Play))
   929     ShutdownNetworkBuffer(&Play->NetBuf);
   930 #endif
   931   ClearList(&(Play->SpyList));
   932   ClearList(&(Play->TipList));
   933   g_date_free(Play->date);
   934   g_free(Play->Name);
   935   g_free(Play->Guns);
   936   g_free(Play->Drugs);
   937   g_free(Play);
   938   return First;
   939 }
   940 
   941 /* 
   942  * Copies player "Src" to player "Dest".
   943  */
   944 void CopyPlayer(Player *Dest, Player *Src)
   945 {
   946   if (!Dest || !Src)
   947     return;
   948   Dest->Turn = Src->Turn;
   949   Dest->Cash = Src->Cash;
   950   Dest->Debt = Src->Debt;
   951   Dest->Bank = Src->Bank;
   952   Dest->Health = Src->Health;
   953   ClearInventory(Dest->Guns, Dest->Drugs);
   954   AddInventory(Dest->Guns, Src->Guns, NumGun);
   955   AddInventory(Dest->Drugs, Src->Drugs, NumDrug);
   956   Dest->CoatSize = Src->CoatSize;
   957   Dest->IsAt = Src->IsAt;
   958   g_free(Dest->Name);
   959   Dest->Name = g_strdup(Src->Name);
   960   Dest->Bitches.Carried = Src->Bitches.Carried;
   961   Dest->Flags = Src->Flags;
   962 }
   963 
   964 gboolean IsCop(Player *Play)
   965 {
   966   return (Play->CopIndex > 0);
   967 }
   968 
   969 char *GetPlayerName(Player *Play)
   970 {
   971   if (Play->Name)
   972     return Play->Name;
   973   else
   974     return "";
   975 }
   976 
   977 void SetPlayerName(Player *Play, char *Name)
   978 {
   979   if (Play->Name)
   980     g_free(Play->Name);
   981   if (!Name)
   982     Play->Name = g_strdup("");
   983   else
   984     Play->Name = g_strdup(Name);
   985 }
   986 
   987 /* 
   988  * Searches the linked list starting at "First" for a Player structure
   989  * with the given ID. Returns a pointer to this structure, or NULL if
   990  * no match can be found.
   991  */
   992 Player *GetPlayerByID(guint ID, GSList *First)
   993 {
   994   GSList *list;
   995   Player *Play;
   996 
   997   for (list = First; list; list = g_slist_next(list)) {
   998     Play = (Player *)list->data;
   999     if (Play->ID == ID)
  1000       return Play;
  1001   }
  1002   return NULL;
  1003 }
  1004 
  1005 /* 
  1006  * Searches the linked list starting at "First" for a Player structure
  1007  * with the name "Name". Returns a pointer to this structure, or NULL
  1008  * if no match can be found.
  1009  */
  1010 Player *GetPlayerByName(char *Name, GSList *First)
  1011 {
  1012   GSList *list;
  1013   Player *Play;
  1014 
  1015   if (Name == NULL || Name[0] == 0)
  1016     return &Noone;
  1017   for (list = First; list; list = g_slist_next(list)) {
  1018     Play = (Player *)list->data;
  1019     if (!IsCop(Play) && strcmp(GetPlayerName(Play), Name) == 0)
  1020       return Play;
  1021   }
  1022   return NULL;
  1023 }
  1024 
  1025 /* 
  1026  * Forms a price based on the string representation in "buf".
  1027  */
  1028 price_t strtoprice(char *buf)
  1029 {
  1030   guint i, buflen, FracNum;
  1031   gchar digit, suffix;
  1032   gboolean minus, InFrac;
  1033   price_t val = 0;
  1034 
  1035   minus = FALSE;
  1036   if (!buf || !buf[0])
  1037     return 0;
  1038 
  1039   buflen = strlen(buf);
  1040   suffix = buf[buflen - 1];
  1041   suffix = toupper(suffix);
  1042   if (suffix == 'M')
  1043     FracNum = 6;
  1044   else if (suffix == 'K')
  1045     FracNum = 3;
  1046   else
  1047     FracNum = 0;
  1048 
  1049   for (i = 0, InFrac = FALSE; i < buflen && (!InFrac || FracNum > 0); i++) {
  1050     digit = buf[i];
  1051     if (digit == '.' || digit == ',') {
  1052       InFrac = TRUE;
  1053     } else if (digit >= '0' && digit <= '9') {
  1054       if (InFrac)
  1055         FracNum--;
  1056       val *= 10;
  1057       val += (digit - '0');
  1058     } else if (digit == '-')
  1059       minus = TRUE;
  1060   }
  1061 
  1062   for (i = 0; i < FracNum; i++)
  1063     val *= 10;
  1064   if (minus)
  1065     val = -val;
  1066   return val;
  1067 }
  1068 
  1069 /* 
  1070  * Prints "price" directly into a dynamically-allocated string buffer
  1071  * and returns a pointer to this buffer. It is the responsbility of
  1072  * the user to g_free this buffer when it is finished with.
  1073  */
  1074 gchar *pricetostr(price_t price)
  1075 {
  1076   GString *PriceStr;
  1077   gchar *NewBuffer;
  1078   price_t absprice;
  1079 
  1080   if (price < 0)
  1081     absprice = -price;
  1082   else
  1083     absprice = price;
  1084   PriceStr = g_string_new(NULL);
  1085   while (absprice != 0) {
  1086     g_string_prepend_c(PriceStr, '0' + (absprice % 10));
  1087     absprice /= 10;
  1088     if (absprice == 0) {
  1089       if (price < 0)
  1090         g_string_prepend_c(PriceStr, '-');
  1091     }
  1092   }
  1093   NewBuffer = PriceStr->str;
  1094   /* Free the string structure, but not the actual char array */
  1095   g_string_free(PriceStr, FALSE);
  1096   return NewBuffer;
  1097 }
  1098 
  1099 /* 
  1100  * Takes the number in "price" and prints it into a dynamically-allocated
  1101  * string, adding commas to split up thousands, and adding a currency
  1102  * symbol to the start. Returns a pointer to the string, which must be
  1103  * g_free'd by the user when it is finished with.
  1104  */
  1105 gchar *FormatPrice(price_t price)
  1106 {
  1107   GString *PriceStr;
  1108   gchar *NewBuffer;
  1109   char thou[10];
  1110   gboolean First = TRUE;
  1111   price_t absprice;
  1112 
  1113   PriceStr = g_string_new(NULL);
  1114   if (price < 0)
  1115     absprice = -price;
  1116   else
  1117     absprice = price;
  1118   while (First || absprice > 0) {
  1119     if (absprice >= 1000)
  1120       sprintf(thou, "%03d", (int)(absprice % 1000l));
  1121     else
  1122       sprintf(thou, "%d", (int)(price % 1000l));
  1123     price /= 1000l;
  1124     absprice /= 1000l;
  1125     if (!First)
  1126       g_string_prepend_c(PriceStr, ',');
  1127     g_string_prepend(PriceStr, thou);
  1128     First = FALSE;
  1129   }
  1130   if (Currency.Prefix)
  1131     g_string_prepend(PriceStr, Currency.Symbol);
  1132   else
  1133     g_string_append(PriceStr, Currency.Symbol);
  1134 
  1135   NewBuffer = PriceStr->str;
  1136   /* Free the string structure only, not the char data */
  1137   g_string_free(PriceStr, FALSE);
  1138   return NewBuffer;
  1139 }
  1140 
  1141 /* 
  1142  * Returns the total number of guns being carried by "Play".
  1143  */
  1144 int TotalGunsCarried(Player *Play)
  1145 {
  1146   int i, c;
  1147 
  1148   c = 0;
  1149   for (i = 0; i < NumGun; i++)
  1150     c += Play->Guns[i].Carried;
  1151   return c;
  1152 }
  1153 
  1154 /* 
  1155  * Capitalises the first character of "string" and writes the resultant
  1156  * string into a dynamically-allocated copy; the user must g_free this
  1157  * string (a pointer to which is returned) when it is no longer needed.
  1158  */
  1159 gchar *InitialCaps(gchar *string)
  1160 {
  1161   gchar *buf;
  1162 
  1163   if (!string)
  1164     return NULL;
  1165   buf = g_strdup(string);
  1166   if (strlen(buf) >= 1)
  1167     buf[0] = toupper(buf[0]);
  1168   return buf;
  1169 }
  1170 
  1171 /* 
  1172  * Returns TRUE if "string" starts with a vowel.
  1173  */
  1174 char StartsWithVowel(char *string)
  1175 {
  1176   int c;
  1177 
  1178   if (!string || strlen(string) < 1)
  1179     return FALSE;
  1180   c = toupper(string[0]);
  1181   return (c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U');
  1182 }
  1183 
  1184 /* 
  1185  * Reads a NULL-terminated string into the buffer "buf" from file "fp".
  1186  * buf is sized to hold the string; this is a dynamic string and must be
  1187  * freed by the calling routine. Returns 0 on success, EOF on failure.
  1188  */
  1189 int read_string(FILE *fp, char **buf)
  1190 {
  1191   int c;
  1192   GString *text;
  1193 
  1194   text = g_string_new("");
  1195   do {
  1196     c = fgetc(fp);
  1197     if (c != EOF && c != 0)
  1198       g_string_append_c(text, (char)c);
  1199   } while (c != EOF && c != 0);
  1200 
  1201   *buf = text->str;
  1202 
  1203   /* Free the GString, but not the actual data text->str */
  1204   g_string_free(text, FALSE);
  1205   if (c == EOF)
  1206     return EOF;
  1207   else
  1208     return 0;
  1209 }
  1210 
  1211 /* 
  1212  * This function simply clears the given inventories "Guns"
  1213  * and "Drugs" if they are non-NULL.
  1214  */
  1215 void ClearInventory(Inventory *Guns, Inventory *Drugs)
  1216 {
  1217   int i;
  1218 
  1219   if (Guns)
  1220     for (i = 0; i < NumGun; i++) {
  1221       Guns[i].Carried = 0;
  1222       Guns[i].TotalValue = 0;
  1223     }
  1224   if (Drugs)
  1225     for (i = 0; i < NumDrug; i++) {
  1226       Drugs[i].Carried = 0;
  1227       Drugs[i].TotalValue = 0;
  1228     }
  1229 }
  1230 
  1231 /* 
  1232  * Returns TRUE only if "Guns" and "Drugs" contain no objects.
  1233  */
  1234 char IsInventoryClear(Inventory *Guns, Inventory *Drugs)
  1235 {
  1236   int i;
  1237 
  1238   if (Guns)
  1239     for (i = 0; i < NumGun; i++)
  1240       if (Guns[i].Carried > 0)
  1241         return FALSE;
  1242   if (Drugs)
  1243     for (i = 0; i < NumDrug; i++)
  1244       if (Drugs[i].Carried > 0)
  1245         return FALSE;
  1246   return TRUE;
  1247 }
  1248 
  1249 /* 
  1250  * Adds inventory "Add" into the contents of inventory "Cumul"
  1251  * Each inventory is of length "Length".
  1252  * N.B. TotalValue is not modified, as it is assumed that the
  1253  * new items are free (if this is not the case it must be
  1254  * handled elsewhere).
  1255  */
  1256 void AddInventory(Inventory *Cumul, Inventory *Add, int Length)
  1257 {
  1258   int i;
  1259 
  1260   for (i = 0; i < Length; i++)
  1261     Cumul[i].Carried += Add[i].Carried;
  1262 }
  1263 
  1264 /* 
  1265  * Given the lists of "Guns" and "Drugs" (which the given player "Play"
  1266  * must have sufficient room to carry) updates the player's space to
  1267  * reflect carrying them.
  1268  */
  1269 void ChangeSpaceForInventory(Inventory *Guns, Inventory *Drugs,
  1270                              Player *Play)
  1271 {
  1272   int i;
  1273 
  1274   if (Guns)
  1275     for (i = 0; i < NumGun; i++) {
  1276       Play->CoatSize -= Guns[i].Carried * Gun[i].Space;
  1277     }
  1278   if (Drugs)
  1279     for (i = 0; i < NumDrug; i++) {
  1280       Play->CoatSize -= Drugs[i].Carried;
  1281     }
  1282 }
  1283 
  1284 /* 
  1285  * Discards items from "Guns" and/or "Drugs" (if non-NULL) if necessary
  1286  * such that player "Play" is able to carry them all. The cheapest
  1287  * objects are discarded.
  1288  */
  1289 void TruncateInventoryFor(Inventory *Guns, Inventory *Drugs, Player *Play)
  1290 {
  1291   int i, Total, CheapIndex;
  1292   int CheapestGun;
  1293 
  1294   Total = 0;
  1295   if (Guns)
  1296     for (i = 0; i < NumGun; i++)
  1297       Total += Guns[i].Carried;
  1298   Total += TotalGunsCarried(Play);
  1299   while (Guns && Total > Play->Bitches.Carried + 2) {
  1300     CheapIndex = -1;
  1301     for (i = 0; i < NumGun; i++)
  1302       if (Guns[i].Carried && (CheapIndex == -1
  1303                               || Gun[i].Price <= Gun[CheapIndex].Price)) {
  1304         CheapIndex = i;
  1305       }
  1306     i = Total - Play->Bitches.Carried - 2;
  1307     if (Guns[CheapIndex].Carried > i) {
  1308       Guns[CheapIndex].Carried -= i;
  1309       Total -= i;
  1310     } else {
  1311       Total -= Guns[CheapIndex].Carried;
  1312       Guns[CheapIndex].Carried = 0;
  1313     }
  1314   }
  1315 
  1316   Total = Play->CoatSize;
  1317   if (Guns)
  1318     for (i = 0; i < NumGun; i++)
  1319       Total -= Guns[i].Carried * Gun[i].Space;
  1320   if (Drugs)
  1321     for (i = 0; i < NumDrug; i++)
  1322       Total -= Drugs[i].Carried;
  1323   while (Total < 0) {
  1324     CheapestGun = -1;
  1325     CheapIndex = -1;
  1326     if (Guns)
  1327       for (i = 0; i < NumGun; i++)
  1328         if (Guns[i].Carried && (CheapIndex == -1
  1329                                 || Gun[i].Price <= Gun[CheapIndex].Price)) {
  1330           CheapIndex = i;
  1331           CheapestGun = Gun[i].Price / Gun[i].Space;
  1332         }
  1333     if (Drugs)
  1334       for (i = 0; i < NumDrug; i++)
  1335         if (Drugs[i].Carried &&
  1336             (CheapIndex == -1 ||
  1337              (CheapestGun == -1
  1338               && Drug[i].MinPrice <= Drug[CheapIndex].MinPrice)
  1339              || (CheapestGun >= 0 && Drug[i].MinPrice <= CheapestGun))) {
  1340           CheapIndex = i;
  1341           CheapestGun = -1;
  1342         }
  1343     if (Guns && CheapestGun >= 0) {
  1344       Guns[CheapIndex].Carried--;
  1345       Total += Gun[CheapIndex].Space;
  1346     } else {
  1347       if (Drugs && Drugs[CheapIndex].Carried >= -Total) {
  1348         Drugs[CheapIndex].TotalValue =
  1349             Drugs[CheapIndex].TotalValue *
  1350             (Drugs[CheapIndex].Carried + Total) /
  1351             Drugs[CheapIndex].Carried;
  1352         Drugs[CheapIndex].Carried += Total;
  1353         Total = 0;
  1354       } else {
  1355         Total += Drugs[CheapIndex].Carried;
  1356         Drugs[CheapIndex].Carried = 0;
  1357         Drugs[CheapIndex].TotalValue = 0;
  1358       }
  1359     }
  1360   }
  1361 }
  1362 
  1363 /* 
  1364  * Returns an index into the drugs array of a random drug that "Play" is
  1365  * carrying at least "amount" of. If no suitable drug is found after 5
  1366  * attempts, returns -1.
  1367  */
  1368 int IsCarryingRandom(Player *Play, int amount)
  1369 {
  1370   int i, ind;
  1371 
  1372   for (i = 0; i < 5; i++) {
  1373     ind = brandom(0, NumDrug);
  1374     if (Play->Drugs[ind].Carried >= amount) {
  1375       return ind;
  1376     }
  1377   }
  1378   return -1;
  1379 }
  1380 
  1381 /* 
  1382  * Returns an index into the "Drugs" array maintained by player "Play"
  1383  * of the next available drug after "OldIndex", following the current
  1384  * sort method (defined globally as "DrugSortMethod").
  1385  */
  1386 int GetNextDrugIndex(int OldIndex, Player *Play)
  1387 {
  1388   int i, MaxIndex;
  1389 
  1390   MaxIndex = -1;
  1391   for (i = 0; i < NumDrug; i++) {
  1392     if (Play->Drugs[i].Price != 0 && i != OldIndex && i != MaxIndex &&
  1393         (MaxIndex == -1
  1394          || (DrugSortMethod == DS_ATOZ
  1395           && g_ascii_strncasecmp(Drug[MaxIndex].Name, Drug[i].Name, strlen(Drug[i].Name)) > 0)
  1396          || (DrugSortMethod == DS_ZTOA
  1397           && g_ascii_strncasecmp(Drug[MaxIndex].Name, Drug[i].Name, strlen(Drug[i].Name)) < 0)
  1398          || (DrugSortMethod == DS_CHEAPFIRST
  1399           && Play->Drugs[MaxIndex].Price > Play->Drugs[i].Price)
  1400          || (DrugSortMethod == DS_CHEAPLAST
  1401           && Play->Drugs[MaxIndex].Price < Play->Drugs[i].Price)) &&
  1402         (OldIndex == -1
  1403          || (DrugSortMethod == DS_ATOZ
  1404           && g_ascii_strncasecmp(Drug[OldIndex].Name, Drug[i].Name, strlen(Drug[i].Name)) <= 0)
  1405          || (DrugSortMethod == DS_ZTOA
  1406           && g_ascii_strncasecmp(Drug[OldIndex].Name, Drug[i].Name, strlen(Drug[i].Name)) >= 0)
  1407          || (DrugSortMethod == DS_CHEAPFIRST
  1408           && Play->Drugs[OldIndex].Price <= Play->Drugs[i].Price)
  1409          || (DrugSortMethod == DS_CHEAPLAST
  1410           && Play->Drugs[OldIndex].Price >= Play->Drugs[i].Price))) {
  1411       MaxIndex = i;
  1412     }
  1413   }
  1414   return MaxIndex;
  1415 }
  1416 
  1417 /* 
  1418  * A DopeList is akin to a Vector class; it is a list of DopeEntry
  1419  * structures, which can be dynamically extended or compressed. This
  1420  * function initializes the newly-created list pointed to by "List"
  1421  * (A DopeEntry contains a Player pointer and a counter, and is used
  1422  * by the server to keep track of tipoffs and spies.)
  1423  */
  1424 void InitList(DopeList *List)
  1425 {
  1426   List->Data = NULL;
  1427   List->Number = 0;
  1428 }
  1429 
  1430 /* 
  1431  * Clears the list pointed to by "List".
  1432  */
  1433 void ClearList(DopeList *List)
  1434 {
  1435   g_free(List->Data);
  1436   InitList(List);
  1437 }
  1438 
  1439 /* 
  1440  * Adds a new DopeEntry (pointed to by "NewEntry") to the list "List".
  1441  * A copy of NewEntry is placed into the list, so the original
  1442  * structure pointed to by NewEntry can be reused.
  1443  */
  1444 void AddListEntry(DopeList *List, DopeEntry *NewEntry)
  1445 {
  1446   if (!NewEntry || !List)
  1447     return;
  1448   List->Number++;
  1449   List->Data = (DopeEntry *)g_realloc(List->Data, List->Number *
  1450                                       sizeof(DopeEntry));
  1451   memmove(&(List->Data[List->Number - 1]), NewEntry, sizeof(DopeEntry));
  1452 }
  1453 
  1454 /* 
  1455  * Removes the DopeEntry at index "Index" from list "List".
  1456  */
  1457 void RemoveListEntry(DopeList *List, int Index)
  1458 {
  1459   if (!List || Index < 0 || Index >= List->Number)
  1460     return;
  1461 
  1462   if (Index < List->Number - 1) {
  1463     memmove(&(List->Data[Index]), &(List->Data[Index + 1]),
  1464               (List->Number - 1 - Index) * sizeof(DopeEntry));
  1465   }
  1466   List->Number--;
  1467   List->Data = (DopeEntry *)g_realloc(List->Data, List->Number *
  1468                                       sizeof(DopeEntry));
  1469   if (List->Number == 0)
  1470     List->Data = NULL;
  1471 }
  1472 
  1473 /* 
  1474  * Returns the index of the DopeEntry matching "Play" in list "List"
  1475  * or -1 if this is not found.
  1476  */
  1477 int GetListEntry(DopeList *List, Player *Play)
  1478 {
  1479   int i;
  1480 
  1481   for (i = List->Number - 1; i >= 0; i--) {
  1482     if (List->Data[i].Play == Play)
  1483       return i;
  1484   }
  1485   return -1;
  1486 }
  1487 
  1488 /* 
  1489  * Removes (if it exists) the DopeEntry in list "List" matching "Play".
  1490  */
  1491 void RemoveListPlayer(DopeList *List, Player *Play)
  1492 {
  1493   RemoveListEntry(List, GetListEntry(List, Play));
  1494 }
  1495 
  1496 /* 
  1497  * Similar to RemoveListPlayer, except that if the list contains "Play" more
  1498  * than once, all the matching entries are removed, not just the first.
  1499  */
  1500 void RemoveAllEntries(DopeList *List, Player *Play)
  1501 {
  1502   int i;
  1503 
  1504   do {
  1505     i = GetListEntry(List, Play);
  1506     if (i >= 0)
  1507       RemoveListEntry(List, i);
  1508   } while (i >= 0);
  1509 }
  1510 
  1511 void ResizeLocations(int NewNum)
  1512 {
  1513   int i;
  1514 
  1515   if (NewNum < NumLocation)
  1516     for (i = NewNum; i < NumLocation; i++) {
  1517       g_free(Location[i].Name);
  1518     }
  1519   Location = g_realloc(Location, sizeof(struct LOCATION) * NewNum);
  1520   if (NewNum > NumLocation) {
  1521     memset(&Location[NumLocation], 0,
  1522            (NewNum - NumLocation) * sizeof(struct LOCATION));
  1523     for (i = NumLocation; i < NewNum; i++) {
  1524       Location[i].Name = g_strdup("");
  1525     }
  1526   }
  1527   NumLocation = NewNum;
  1528 }
  1529 
  1530 void ResizeCops(int NewNum)
  1531 {
  1532   int i;
  1533 
  1534   if (NewNum < NumCop)
  1535     for (i = NewNum; i < NumCop; i++) {
  1536       g_free(Cop[i].Name);
  1537       g_free(Cop[i].DeputyName);
  1538       g_free(Cop[i].DeputiesName);
  1539     }
  1540   Cop = g_realloc(Cop, sizeof(struct COP) * NewNum);
  1541   if (NewNum > NumCop) {
  1542     memset(&Cop[NumCop], 0, (NewNum - NumCop) * sizeof(struct COP));
  1543     for (i = NumCop; i < NewNum; i++) {
  1544       Cop[i].Name = g_strdup("");
  1545       Cop[i].DeputyName = g_strdup("");
  1546       Cop[i].DeputiesName = g_strdup("");
  1547     }
  1548   }
  1549   NumCop = NewNum;
  1550 }
  1551 
  1552 void ResizeGuns(int NewNum)
  1553 {
  1554   int i;
  1555 
  1556   if (NewNum < NumGun)
  1557     for (i = NewNum; i < NumGun; i++) {
  1558       g_free(Gun[i].Name);
  1559     }
  1560   Gun = g_realloc(Gun, sizeof(struct GUN) * NewNum);
  1561   if (NewNum > NumGun) {
  1562     memset(&Gun[NumGun], 0, (NewNum - NumGun) * sizeof(struct GUN));
  1563     for (i = NumGun; i < NewNum; i++) {
  1564       Gun[i].Name = g_strdup("");
  1565     }
  1566   }
  1567   NumGun = NewNum;
  1568 }
  1569 
  1570 void ResizeDrugs(int NewNum)
  1571 {
  1572   int i;
  1573 
  1574   if (NewNum < NumDrug)
  1575     for (i = NewNum; i < NumDrug; i++) {
  1576       g_free(Drug[i].Name);
  1577       g_free(Drug[i].CheapStr);
  1578     }
  1579   Drug = g_realloc(Drug, sizeof(struct DRUG) * NewNum);
  1580   if (NewNum > NumDrug) {
  1581     memset(&Drug[NumDrug], 0, (NewNum - NumDrug) * sizeof(struct DRUG));
  1582     for (i = NumDrug; i < NewNum; i++) {
  1583       Drug[i].Name = g_strdup("");
  1584       Drug[i].CheapStr = g_strdup("");
  1585     }
  1586   }
  1587   NumDrug = NewNum;
  1588 }
  1589 
  1590 void ResizeSubway(int NewNum)
  1591 {
  1592   int i;
  1593 
  1594   if (NewNum < NumSubway)
  1595     for (i = NewNum; i < NumSubway; i++) {
  1596       g_free(SubwaySaying[i]);
  1597     }
  1598   SubwaySaying = g_realloc(SubwaySaying, sizeof(char *) * NewNum);
  1599   if (NewNum > NumSubway)
  1600     for (i = NumSubway; i < NewNum; i++) {
  1601       SubwaySaying[i] = g_strdup("");
  1602     }
  1603   NumSubway = NewNum;
  1604 }
  1605 
  1606 void ResizePlaying(int NewNum)
  1607 {
  1608   int i;
  1609 
  1610   if (NewNum < NumPlaying)
  1611     for (i = NewNum; i < NumPlaying; i++) {
  1612       g_free(Playing[i]);
  1613     }
  1614   Playing = g_realloc(Playing, sizeof(char *) * NewNum);
  1615   if (NewNum > NumPlaying)
  1616     for (i = NumPlaying; i < NewNum; i++) {
  1617       Playing[i] = g_strdup("");
  1618     }
  1619   NumPlaying = NewNum;
  1620 }
  1621 
  1622 void ResizeStoppedTo(int NewNum)
  1623 {
  1624   int i;
  1625 
  1626   if (NewNum < NumStoppedTo)
  1627     for (i = NewNum; i < NumStoppedTo; i++) {
  1628       g_free(StoppedTo[i]);
  1629     }
  1630   StoppedTo = g_realloc(StoppedTo, sizeof(char *) * NewNum);
  1631   if (NewNum > NumStoppedTo)
  1632     for (i = NumStoppedTo; i < NewNum; i++) {
  1633       StoppedTo[i] = g_strdup("");
  1634     }
  1635   NumStoppedTo = NewNum;
  1636 }
  1637 
  1638 /* 
  1639  * Sets the dynamically-sized string pointed to by *dest to a copy of
  1640  * "src" - src can safely be freed or reused afterwards. Any existing
  1641  * string in "dest" is freed. The function returns immediately if src
  1642  * and *dest are already the same.
  1643  */
  1644 void AssignName(gchar **dest, gchar *src)
  1645 {
  1646   if (*dest == src)
  1647     return;
  1648   g_free(*dest);
  1649   *dest = g_strdup(src);
  1650 }
  1651 
  1652 void CopyNames(struct NAMES *dest, struct NAMES *src)
  1653 {
  1654   AssignName(&dest->Bitch, _(src->Bitch));
  1655   AssignName(&dest->Bitches, _(src->Bitches));
  1656   AssignName(&dest->Gun, _(src->Gun));
  1657   AssignName(&dest->Guns, _(src->Guns));
  1658   AssignName(&dest->Drug, _(src->Drug));
  1659   AssignName(&dest->Drugs, _(src->Drugs));
  1660   AssignName(&dest->Date, _(src->Date));
  1661   AssignName(&dest->LoanSharkName, _(src->LoanSharkName));
  1662   AssignName(&dest->BankName, _(src->BankName));
  1663   AssignName(&dest->GunShopName, _(src->GunShopName));
  1664   AssignName(&dest->RoughPubName, _(src->RoughPubName));
  1665 }
  1666 
  1667 #ifdef NETWORKING
  1668 void CopyMetaServer(struct METASERVER *dest, struct METASERVER *src)
  1669 {
  1670   dest->Active = src->Active;
  1671   AssignName(&dest->URL, src->URL);
  1672   AssignName(&dest->LocalName, src->LocalName);
  1673   AssignName(&dest->Password, src->Password);
  1674   AssignName(&dest->Comment, src->Comment);
  1675 }
  1676 #endif
  1677 
  1678 void CopyLocation(struct LOCATION *dest, struct LOCATION *src)
  1679 {
  1680   AssignName(&dest->Name, _(src->Name));
  1681   dest->PolicePresence = src->PolicePresence;
  1682   dest->MinDrug = src->MinDrug;
  1683   dest->MaxDrug = src->MaxDrug;
  1684 }
  1685 
  1686 void CopyCop(struct COP *dest, struct COP *src)
  1687 {
  1688   AssignName(&dest->Name, _(src->Name));
  1689   AssignName(&dest->DeputyName, _(src->DeputyName));
  1690   AssignName(&dest->DeputiesName, _(src->DeputiesName));
  1691   dest->Armor = src->Armor;
  1692   dest->DeputyArmor = src->DeputyArmor;
  1693   dest->AttackPenalty = src->AttackPenalty;
  1694   dest->DefendPenalty = src->DefendPenalty;
  1695   dest->MinDeputies = src->MinDeputies;
  1696   dest->MaxDeputies = src->MaxDeputies;
  1697   dest->GunIndex = src->GunIndex;
  1698   dest->CopGun = src->CopGun;
  1699   dest->DeputyGun = src->DeputyGun;
  1700 }
  1701 
  1702 void CopyGun(struct GUN *dest, struct GUN *src)
  1703 {
  1704   AssignName(&dest->Name, _(src->Name));
  1705   dest->Price = src->Price;
  1706   dest->Space = src->Space;
  1707   dest->Damage = src->Damage;
  1708 }
  1709 
  1710 void CopyDrug(struct DRUG *dest, struct DRUG *src)
  1711 {
  1712   AssignName(&dest->Name, src->Name[0] ? _(src->Name) : "");
  1713   dest->MinPrice = src->MinPrice;
  1714   dest->MaxPrice = src->MaxPrice;
  1715   dest->Cheap = src->Cheap;
  1716   dest->Expensive = src->Expensive;
  1717   AssignName(&dest->CheapStr, src->CheapStr[0] ? _(src->CheapStr) : "");
  1718 }
  1719 
  1720 void CopyDrugs(struct DRUGS *dest, struct DRUGS *src)
  1721 {
  1722   AssignName(&dest->ExpensiveStr1, _(src->ExpensiveStr1));
  1723   AssignName(&dest->ExpensiveStr2, _(src->ExpensiveStr2));
  1724   dest->CheapDivide = src->CheapDivide;
  1725   dest->ExpensiveMultiply = src->ExpensiveMultiply;
  1726 }
  1727 
  1728 static struct PRICES BackupPrices;
  1729 static struct NAMES BackupNames;
  1730 static struct DRUG *BackupDrug = NULL;
  1731 static struct GUN *BackupGun = NULL;
  1732 static struct LOCATION *BackupLocation = NULL;
  1733 static struct CURRENCY BackupCurrency = { NULL, TRUE };
  1734 static gint NumBackupDrug = 0, NumBackupGun = 0, NumBackupLocation = 0;
  1735 
  1736 void BackupConfig(void)
  1737 {
  1738   gint i;
  1739 
  1740   BackupPrices.Spy = Prices.Spy;
  1741   BackupPrices.Tipoff = Prices.Tipoff;
  1742   AssignName(&BackupCurrency.Symbol, Currency.Symbol);
  1743   BackupCurrency.Prefix = Currency.Prefix;
  1744   CopyNames(&BackupNames, &Names);
  1745 
  1746   /* Free existing backups of guns, drugs, and locations */
  1747   for (i = 0; i < NumBackupGun; i++)
  1748     g_free(BackupGun[i].Name);
  1749   g_free(BackupGun);
  1750   for (i = 0; i < NumBackupDrug; i++) {
  1751     g_free(BackupDrug[i].Name);
  1752     g_free(BackupDrug[i].CheapStr);
  1753   }
  1754   g_free(BackupDrug);
  1755   for (i = 0; i < NumBackupLocation; i++)
  1756     g_free(BackupLocation[i].Name);
  1757   g_free(BackupLocation);
  1758 
  1759   NumBackupGun = NumGun;
  1760   BackupGun = g_new0(struct GUN, NumGun);
  1761 
  1762   for (i = 0; i < NumGun; i++)
  1763     CopyGun(&BackupGun[i], &Gun[i]);
  1764 
  1765   NumBackupDrug = NumDrug;
  1766   BackupDrug = g_new0(struct DRUG, NumDrug);
  1767 
  1768   for (i = 0; i < NumDrug; i++)
  1769     CopyDrug(&BackupDrug[i], &Drug[i]);
  1770 
  1771   NumBackupLocation = NumLocation;
  1772   BackupLocation = g_new0(struct LOCATION, NumLocation);
  1773 
  1774   for (i = 0; i < NumLocation; i++)
  1775     CopyLocation(&BackupLocation[i], &Location[i]);
  1776 }
  1777 
  1778 void RestoreConfig(void)
  1779 {
  1780   gint i;
  1781 
  1782   Prices.Spy = BackupPrices.Spy;
  1783   Prices.Tipoff = BackupPrices.Tipoff;
  1784   CopyNames(&Names, &BackupNames);
  1785   AssignName(&Currency.Symbol, BackupCurrency.Symbol);
  1786   Currency.Prefix = BackupCurrency.Prefix;
  1787 
  1788   ResizeGuns(NumBackupGun);
  1789   for (i = 0; i < NumGun; i++)
  1790     CopyGun(&Gun[i], &BackupGun[i]);
  1791   ResizeDrugs(NumBackupDrug);
  1792   for (i = 0; i < NumDrug; i++)
  1793     CopyDrug(&Drug[i], &BackupDrug[i]);
  1794   ResizeLocations(NumBackupLocation);
  1795   for (i = 0; i < NumLocation; i++)
  1796     CopyLocation(&Location[i], &BackupLocation[i]);
  1797 }
  1798 
  1799 void ScannerErrorHandler(GScanner *scanner, gchar *msg, gint error)
  1800 {
  1801   g_print("%s\n", msg);
  1802 }
  1803 
  1804 /*
  1805  * On Windows systems, check the current config file referenced by "scanner"
  1806  * for a UTF-8 header. If one is found, "conv" and "encoding" are set
  1807  * for UTF-8 encoding.
  1808  */
  1809 static void CheckConfigHeader(GScanner *scanner, Converter *conv,
  1810                               gchar **encoding)
  1811 {
  1812 #ifdef CYGWIN
  1813   GTokenType token;
  1814 
  1815   token = g_scanner_peek_next_token(scanner);
  1816   if (token == (guchar)'\357') {
  1817     /* OK; assume this is a Windows-style \357 \273 \277 UTF-8 header */
  1818     if (g_scanner_get_next_token(scanner) != (guchar)'\357'
  1819         || g_scanner_get_next_token(scanner) != (guchar)'\273'
  1820         || g_scanner_get_next_token(scanner) != (guchar)'\277') {
  1821       return;
  1822     }
  1823     Conv_SetCodeset(conv, "UTF-8");
  1824     if (encoding) {
  1825       g_free(*encoding);
  1826       *encoding = g_strdup("UTF-8");
  1827     }
  1828   }
  1829 #endif
  1830 }
  1831 
  1832 /* 
  1833  * Read a configuration file given by "FileName"
  1834  */
  1835 static gboolean ReadConfigFile(char *FileName, gchar **encoding)
  1836 {
  1837   FILE *fp;
  1838   Converter *conv;
  1839 
  1840   GScanner *scanner;
  1841 
  1842   fp = fopen(FileName, "r");
  1843   if (fp) {
  1844     conv = Conv_New();
  1845     if (encoding) {
  1846       *encoding = NULL;
  1847     }
  1848     scanner = g_scanner_new(&ScannerConfig);
  1849     scanner->input_name = FileName;
  1850     scanner->msg_handler = ScannerErrorHandler;
  1851     g_scanner_input_file(scanner, fileno(fp));
  1852     CheckConfigHeader(scanner, conv, encoding);
  1853     while (!g_scanner_eof(scanner)) {
  1854       if (!ParseNextConfig(scanner, conv, encoding, FALSE)) {
  1855         ConfigErrors++;
  1856         g_scanner_error(scanner,
  1857                         _("Unable to process configuration file %s, line %d"),
  1858                         FileName, g_scanner_cur_line(scanner));
  1859       }
  1860     }
  1861     g_scanner_destroy(scanner);
  1862     Conv_Free(conv);
  1863     fclose(fp);
  1864     return TRUE;
  1865   } else {
  1866     return FALSE;
  1867   }
  1868 }
  1869 
  1870 gboolean ParseNextConfig(GScanner *scanner, Converter *conv,
  1871                          gchar **encoding, gboolean print)
  1872 {
  1873   GTokenType token;
  1874   gchar *ID1, *ID2;
  1875   gulong ind = 0;
  1876   int GlobalIndex;
  1877   gboolean IndexGiven = FALSE;
  1878 
  1879   ID1 = ID2 = NULL;
  1880   token = g_scanner_get_next_token(scanner);
  1881   if (token == G_TOKEN_EOF)
  1882     return TRUE;
  1883   if (token != G_TOKEN_IDENTIFIER) {
  1884     g_scanner_unexp_token(scanner, G_TOKEN_IDENTIFIER, NULL, NULL,
  1885                           NULL, NULL, FALSE);
  1886     return FALSE;
  1887   }
  1888 
  1889   if (g_ascii_strncasecmp(scanner->value.v_identifier, "include", 7) == 0) {
  1890     token = g_scanner_get_next_token(scanner);
  1891     if (token == G_TOKEN_STRING) {
  1892       if (!ReadConfigFile(scanner->value.v_string, NULL)) {
  1893         g_scanner_error(scanner, _("Unable to open file %s"),
  1894                         scanner->value.v_string);
  1895       }
  1896       return TRUE;
  1897     } else {
  1898       g_scanner_unexp_token(scanner, G_TOKEN_STRING, NULL, NULL,
  1899                             NULL, NULL, FALSE);
  1900       return FALSE;
  1901     }
  1902   } else if (g_ascii_strncasecmp(scanner->value.v_identifier, "encoding", 8) == 0) {
  1903     token = g_scanner_get_next_token(scanner);
  1904     if (token == G_TOKEN_STRING) {
  1905       Conv_SetCodeset(conv, scanner->value.v_string);
  1906       if (encoding) {
  1907         g_free(*encoding);
  1908         *encoding = g_strdup(scanner->value.v_string);
  1909       }
  1910       return TRUE;
  1911     } else {
  1912       g_scanner_unexp_token(scanner, G_TOKEN_STRING, NULL, NULL,
  1913                             NULL, NULL, FALSE);
  1914       return FALSE;
  1915     }
  1916   }
  1917 
  1918   ID1 = g_strdup(scanner->value.v_identifier);
  1919   token = g_scanner_get_next_token(scanner);
  1920   if (token == G_TOKEN_LEFT_BRACE) {
  1921     token = g_scanner_get_next_token(scanner);
  1922     if (token != G_TOKEN_INT) {
  1923       g_scanner_unexp_token(scanner, G_TOKEN_INT, NULL, NULL,
  1924                             NULL, NULL, FALSE);
  1925       return FALSE;
  1926     }
  1927     ind = scanner->value.v_int;
  1928     IndexGiven = TRUE;
  1929     token = g_scanner_get_next_token(scanner);
  1930     if (token != G_TOKEN_RIGHT_BRACE) {
  1931       g_scanner_unexp_token(scanner, G_TOKEN_RIGHT_BRACE, NULL, NULL,
  1932                             NULL, NULL, FALSE);
  1933       return FALSE;
  1934     }
  1935     token = g_scanner_get_next_token(scanner);
  1936     if (token == '.') {
  1937       token = g_scanner_get_next_token(scanner);
  1938       if (token != G_TOKEN_IDENTIFIER) {
  1939         g_scanner_unexp_token(scanner, G_TOKEN_IDENTIFIER, NULL, NULL,
  1940                               NULL, NULL, FALSE);
  1941         return FALSE;
  1942       }
  1943       ID2 = g_strdup(scanner->value.v_identifier);
  1944       token = g_scanner_get_next_token(scanner);
  1945     }
  1946   }
  1947   GlobalIndex = GetGlobalIndex(ID1, ID2);
  1948   g_free(ID1);
  1949   g_free(ID2);
  1950   if (GlobalIndex == -1)
  1951     return FALSE;
  1952   if (token == G_TOKEN_EOF) {
  1953     PrintConfigValue(GlobalIndex, (int)ind, IndexGiven, scanner);
  1954     return TRUE;
  1955   } else if (token == G_TOKEN_EQUAL_SIGN) {
  1956     if (CountPlayers(FirstServer) > 0) {
  1957       g_warning(_("Configuration can only be changed interactively "
  1958                   "when no\nplayers are logged on. Wait for all "
  1959                   "players to log off, or remove\nthem with the "
  1960                   "push or kill commands, and try again."));
  1961     } else {
  1962       if (SetConfigValue(GlobalIndex, (int)ind, IndexGiven, conv, scanner)
  1963           && print) {
  1964         PrintConfigValue(GlobalIndex, (int)ind, IndexGiven, scanner);
  1965       }
  1966     }
  1967     return TRUE;
  1968   } else {
  1969     return FALSE;
  1970   }
  1971   return FALSE;
  1972 }
  1973 
  1974 int GetGlobalIndex(gchar *ID1, gchar *ID2)
  1975 {
  1976   int i;
  1977   const int NumGlob = sizeof(Globals) / sizeof(Globals[0]);
  1978 
  1979   if (!ID1)
  1980     return -1;
  1981   for (i = 0; i < NumGlob; i++) {
  1982     if (!ID2 && !Globals[i].NameStruct[0]
  1983         && g_ascii_strcasecmp(ID1, Globals[i].Name) == 0) {
  1984       /* Just a bog-standard ID1=value */
  1985       return i;
  1986     }
  1987     if (g_ascii_strcasecmp(ID1, Globals[i].NameStruct) == 0 && ID2
  1988         && g_ascii_strcasecmp(ID2, Globals[i].Name) == 0
  1989         && Globals[i].StructStaticPt && Globals[i].StructListPt) {
  1990       /* ID1[index].ID2=value */
  1991       return i;
  1992     }
  1993   }
  1994   return -1;
  1995 }
  1996 
  1997 static void *GetGlobalPointer(int GlobalIndex, int StructIndex)
  1998 {
  1999   void *ValPt = NULL;
  2000 
  2001   if (Globals[GlobalIndex].IntVal) {
  2002     ValPt = (void *)Globals[GlobalIndex].IntVal;
  2003   } else if (Globals[GlobalIndex].PriceVal) {
  2004     ValPt = (void *)Globals[GlobalIndex].PriceVal;
  2005   } else if (Globals[GlobalIndex].BoolVal) {
  2006     ValPt = (void *)Globals[GlobalIndex].BoolVal;
  2007   } else if (Globals[GlobalIndex].StringVal) {
  2008     ValPt = (void *)Globals[GlobalIndex].StringVal;
  2009   }
  2010   if (!ValPt)
  2011     return NULL;
  2012 
  2013   if (Globals[GlobalIndex].StructStaticPt &&
  2014       Globals[GlobalIndex].StructListPt) {
  2015     return (char *)ValPt - (char *)Globals[GlobalIndex].StructStaticPt +
  2016         (char *)*(Globals[GlobalIndex].StructListPt) +
  2017         (StructIndex - 1) * Globals[GlobalIndex].LenStruct;
  2018   } else {
  2019     return ValPt;
  2020   }
  2021 }
  2022 
  2023 gchar **GetGlobalString(int GlobalIndex, int StructIndex)
  2024 {
  2025   g_assert(Globals[GlobalIndex].StringVal);
  2026 
  2027   return (gchar **)GetGlobalPointer(GlobalIndex, StructIndex);
  2028 }
  2029 
  2030 gint *GetGlobalInt(int GlobalIndex, int StructIndex)
  2031 {
  2032   g_assert(Globals[GlobalIndex].IntVal);
  2033 
  2034   return (gint *)GetGlobalPointer(GlobalIndex, StructIndex);
  2035 }
  2036 
  2037 price_t *GetGlobalPrice(int GlobalIndex, int StructIndex)
  2038 {
  2039   g_assert(Globals[GlobalIndex].PriceVal);
  2040 
  2041   return (price_t *)GetGlobalPointer(GlobalIndex, StructIndex);
  2042 }
  2043 
  2044 gboolean *GetGlobalBoolean(int GlobalIndex, int StructIndex)
  2045 {
  2046   g_assert(Globals[GlobalIndex].BoolVal);
  2047 
  2048   return (gboolean *)GetGlobalPointer(GlobalIndex, StructIndex);
  2049 }
  2050 
  2051 gchar ***GetGlobalStringList(int GlobalIndex, int StructIndex)
  2052 {
  2053   g_assert(Globals[GlobalIndex].StringList);
  2054 
  2055   return (gchar ***)GetGlobalPointer(GlobalIndex, StructIndex);
  2056 }
  2057 
  2058 gboolean CheckMaxIndex(GScanner *scanner, int GlobalIndex, int StructIndex,
  2059                        gboolean IndexGiven)
  2060 {
  2061   if (!Globals[GlobalIndex].MaxIndex ||
  2062       (Globals[GlobalIndex].StringList && !IndexGiven) ||
  2063       (IndexGiven && StructIndex >= 1 &&
  2064        StructIndex <= *(Globals[GlobalIndex].MaxIndex))) {
  2065     return TRUE;
  2066   }
  2067   /* Error message displayed when you try to set, for example,
  2068    * Drug[10].Name when NumDrug<10 (%s="Drug" and %d=10 in this example) */
  2069   g_scanner_error(scanner,
  2070                   _("Index into %s array should be between 1 and %d"),
  2071                   (Globals[GlobalIndex].NameStruct
  2072                    && Globals[GlobalIndex].
  2073                    NameStruct[0]) ? Globals[GlobalIndex].
  2074                   NameStruct : Globals[GlobalIndex].Name,
  2075                   *(Globals[GlobalIndex].MaxIndex));
  2076   return FALSE;
  2077 }
  2078 
  2079 void PrintConfigValue(int GlobalIndex, int StructIndex,
  2080                       gboolean IndexGiven, GScanner *scanner)
  2081 {
  2082   gchar *GlobalName;
  2083   int i;
  2084 
  2085   if (!CheckMaxIndex(scanner, GlobalIndex, StructIndex, IndexGiven))
  2086     return;
  2087   if (Globals[GlobalIndex].NameStruct[0]) {
  2088     GlobalName =
  2089         g_strdup_printf("%s[%d].%s", Globals[GlobalIndex].NameStruct,
  2090                         StructIndex, Globals[GlobalIndex].Name);
  2091   } else
  2092     GlobalName = Globals[GlobalIndex].Name;
  2093   if (Globals[GlobalIndex].IntVal) {
  2094     /* Display of a numeric config. file variable - e.g. "NumDrug is 6" */
  2095     g_print(_("%s is %d\n"), GlobalName,
  2096             *GetGlobalInt(GlobalIndex, StructIndex));
  2097   } else if (Globals[GlobalIndex].BoolVal) {
  2098     /* Display of a boolean config. file variable - e.g. "DrugValue is
  2099        TRUE" */
  2100     g_print(_("%s is %s\n"), GlobalName,
  2101             *GetGlobalBoolean(GlobalIndex, StructIndex) ?
  2102             "TRUE" : "FALSE");
  2103   } else if (Globals[GlobalIndex].PriceVal) {
  2104     /* Display of a price config. file variable - e.g. "Bitch.MinPrice is
  2105        $200" */
  2106     dpg_print(_("%s is %P\n"), GlobalName,
  2107               *GetGlobalPrice(GlobalIndex, StructIndex));
  2108   } else if (Globals[GlobalIndex].StringVal) {
  2109     /* Display of a string config. file variable - e.g. "LoanSharkName is
  2110        \"the loan shark\"" */
  2111     g_print(_("%s is \"%s\"\n"), GlobalName,
  2112             *GetGlobalString(GlobalIndex, StructIndex));
  2113   } else if (Globals[GlobalIndex].StringList) {
  2114     if (IndexGiven) {
  2115       /* Display of an indexed string list config. file variable - e.g.
  2116          "StoppedTo[1] is have a beer" */
  2117       g_print(_("%s[%d] is %s\n"), GlobalName, StructIndex,
  2118               (*(Globals[GlobalIndex].StringList))[StructIndex - 1]);
  2119     } else {
  2120       GString *text;
  2121 
  2122       text = g_string_new("");
  2123       /* Display of the first part of an entire string list config. file
  2124          variable - e.g. "StoppedTo is { " (followed by "have a beer",
  2125          "smoke a joint" etc.) */
  2126       g_string_printf(text, _("%s is { "), GlobalName);
  2127       if (Globals[GlobalIndex].MaxIndex) {
  2128         for (i = 0; i < *(Globals[GlobalIndex].MaxIndex); i++) {
  2129           if (i > 0)
  2130             g_string_append(text, ", ");
  2131           g_string_append_printf(text, "\"%s\"",
  2132                             (*(Globals[GlobalIndex].StringList))[i]);
  2133         }
  2134       }
  2135       g_string_append(text, " }\n");
  2136 
  2137       g_print("%s", text->str);
  2138       g_string_free(text, TRUE);
  2139     }
  2140   }
  2141   if (Globals[GlobalIndex].NameStruct[0])
  2142     g_free(GlobalName);
  2143 }
  2144 
  2145 static gboolean SetConfigValue(int GlobalIndex, int StructIndex,
  2146                                gboolean IndexGiven, Converter *conv,
  2147                                GScanner *scanner)
  2148 {
  2149   gchar *GlobalName, *tmpstr;
  2150   GTokenType token;
  2151   int IntVal, NewNum;
  2152   Player *tmp;
  2153   GSList *list, *StartList;
  2154   gboolean parsed;
  2155 
  2156   if (!CheckMaxIndex(scanner, GlobalIndex, StructIndex, IndexGiven))
  2157     return FALSE;
  2158   if (Globals[GlobalIndex].NameStruct[0]) {
  2159     GlobalName =
  2160         g_strdup_printf("%s[%d].%s", Globals[GlobalIndex].NameStruct,
  2161                         StructIndex, Globals[GlobalIndex].Name);
  2162   } else {
  2163     GlobalName = Globals[GlobalIndex].Name;
  2164   }
  2165   if (Globals[GlobalIndex].IntVal) {
  2166     gboolean minus = FALSE;
  2167 
  2168     /* GScanner doesn't understand negative numbers, so we need to
  2169      * explicitly check for a prefixed minus sign */
  2170     token = g_scanner_get_next_token(scanner);
  2171     if (token == '-') {
  2172       minus = TRUE;
  2173       token = g_scanner_get_next_token(scanner);
  2174     }
  2175     if (token == G_TOKEN_INT) {
  2176       IntVal = (int)scanner->value.v_int;
  2177       if (minus) {
  2178         IntVal = -IntVal;
  2179       }
  2180       if (IntVal < Globals[GlobalIndex].MinVal) {
  2181         g_scanner_warn(scanner, _("%s can be no smaller than %d - ignoring!"),
  2182                        GlobalName, Globals[GlobalIndex].MinVal);
  2183         return FALSE;
  2184       }
  2185       if (Globals[GlobalIndex].MaxVal > Globals[GlobalIndex].MinVal
  2186           && IntVal > Globals[GlobalIndex].MaxVal) {
  2187         g_scanner_warn(scanner, _("%s can be no larger than %d - ignoring!"),
  2188                        GlobalName, Globals[GlobalIndex].MaxVal);
  2189         return FALSE;
  2190       }
  2191       if (Globals[GlobalIndex].ResizeFunc) {
  2192         (*(Globals[GlobalIndex].ResizeFunc)) (IntVal);
  2193         /* Displayed, for example, when you set NumDrug=10 to allow
  2194            Drug[10].Name etc. to be set */
  2195         if (ConfigVerbose)
  2196           g_print(_("Resized structure list to %d elements\n"), IntVal);
  2197         for (list = FirstClient; list; list = g_slist_next(list)) {
  2198           tmp = (Player *)list->data;
  2199           UpdatePlayer(tmp);
  2200         }
  2201         for (list = FirstServer; list; list = g_slist_next(list)) {
  2202           tmp = (Player *)list->data;
  2203           UpdatePlayer(tmp);
  2204         }
  2205       }
  2206       *GetGlobalInt(GlobalIndex, StructIndex) = IntVal;
  2207     } else {
  2208       g_scanner_unexp_token(scanner, G_TOKEN_INT, NULL, NULL,
  2209                             NULL, NULL, FALSE);
  2210       return FALSE;
  2211     }
  2212   } else if (Globals[GlobalIndex].BoolVal) {
  2213     scanner->config->cset_identifier_first =
  2214         G_CSET_a_2_z "01" G_CSET_A_2_Z;
  2215     scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z;
  2216     token = g_scanner_get_next_token(scanner);
  2217     scanner->config->cset_identifier_first = G_CSET_a_2_z "_" G_CSET_A_2_Z;
  2218     scanner->config->cset_identifier_nth =
  2219         G_CSET_a_2_z "._0123456789" G_CSET_A_2_Z;
  2220     parsed = FALSE;
  2221     if (token == G_TOKEN_IDENTIFIER) {
  2222       if (g_ascii_strncasecmp(scanner->value.v_identifier, "TRUE", 4) == 0 ||
  2223           strcmp(scanner->value.v_identifier, "1") == 0) {
  2224         parsed = TRUE;
  2225         *GetGlobalBoolean(GlobalIndex, StructIndex) = TRUE;
  2226       } else if (g_ascii_strncasecmp(scanner->value.v_identifier, "FALSE", 5) == 0
  2227                  || strcmp(scanner->value.v_identifier, "0") == 0) {
  2228         parsed = TRUE;
  2229         *GetGlobalBoolean(GlobalIndex, StructIndex) = FALSE;
  2230       }
  2231     }
  2232     if (!parsed) {
  2233       g_scanner_unexp_token(scanner, G_TOKEN_NONE, NULL, NULL, NULL,
  2234                             _("expected a boolean value (one of 0, FALSE, "
  2235                               "1, TRUE)"), FALSE);
  2236       return FALSE;
  2237     }
  2238   } else if (Globals[GlobalIndex].PriceVal) {
  2239     token = g_scanner_get_next_token(scanner);
  2240     if (token == G_TOKEN_INT) {
  2241       *GetGlobalPrice(GlobalIndex, StructIndex) =
  2242           (price_t)scanner->value.v_int;
  2243     } else {
  2244       g_scanner_unexp_token(scanner, G_TOKEN_INT, NULL, NULL,
  2245                             NULL, NULL, FALSE);
  2246       return FALSE;
  2247     }
  2248   } else if (Globals[GlobalIndex].StringVal) {
  2249     scanner->config->identifier_2_string = TRUE;
  2250     scanner->config->cset_identifier_first =
  2251         G_CSET_a_2_z "._0123456789" G_CSET_A_2_Z G_CSET_LATINS
  2252         G_CSET_LATINC;
  2253     scanner->config->cset_identifier_nth =
  2254         G_CSET_a_2_z " ._0123456789" G_CSET_A_2_Z G_CSET_LATINS
  2255         G_CSET_LATINC;
  2256     token = g_scanner_get_next_token(scanner);
  2257     if (token == G_TOKEN_STRING) {
  2258       tmpstr = Conv_ToInternal(conv, scanner->value.v_string, -1);
  2259       AssignName(GetGlobalString(GlobalIndex, StructIndex), tmpstr);
  2260       g_free(tmpstr);
  2261     } else if (token == G_TOKEN_IDENTIFIER) {
  2262       tmpstr = Conv_ToInternal(conv, scanner->value.v_identifier, -1);
  2263       AssignName(GetGlobalString(GlobalIndex, StructIndex), tmpstr);
  2264       g_free(tmpstr);
  2265     } else {
  2266       g_scanner_unexp_token(scanner, G_TOKEN_STRING, NULL, NULL,
  2267                             NULL, NULL, FALSE);
  2268     }
  2269     scanner->config->identifier_2_string = FALSE;
  2270     scanner->config->cset_identifier_first = G_CSET_a_2_z "_" G_CSET_A_2_Z;
  2271     scanner->config->cset_identifier_nth =
  2272         G_CSET_a_2_z "._0123456789" G_CSET_A_2_Z;
  2273   } else if (Globals[GlobalIndex].StringList) {
  2274     token = g_scanner_get_next_token(scanner);
  2275     if (IndexGiven) {
  2276       if (token == G_TOKEN_STRING) {
  2277         tmpstr = Conv_ToInternal(conv, scanner->value.v_string, -1);
  2278         AssignName(&(*(Globals[GlobalIndex].StringList))[StructIndex - 1],
  2279                    tmpstr);
  2280         g_free(tmpstr);
  2281       } else {
  2282         g_scanner_unexp_token(scanner, G_TOKEN_STRING, NULL, NULL,
  2283                               NULL, NULL, FALSE);
  2284         return FALSE;
  2285       }
  2286     } else {
  2287       StartList = NULL;
  2288       if (token != G_TOKEN_LEFT_CURLY) {
  2289         g_scanner_unexp_token(scanner, G_TOKEN_LEFT_CURLY, NULL, NULL,
  2290                               NULL, NULL, FALSE);
  2291         return FALSE;
  2292       }
  2293       NewNum = 0;
  2294       do {
  2295         token = g_scanner_get_next_token(scanner);
  2296         if (token == G_TOKEN_STRING) {
  2297           tmpstr = g_strdup(scanner->value.v_string);
  2298           NewNum++;
  2299           StartList = g_slist_append(StartList, tmpstr);
  2300         } else if (token != G_TOKEN_RIGHT_CURLY && token != G_TOKEN_COMMA) {
  2301           g_scanner_unexp_token(scanner, G_TOKEN_STRING, NULL, NULL,
  2302                                 NULL, NULL, FALSE);
  2303           return FALSE;
  2304         }
  2305       } while (token != G_TOKEN_RIGHT_CURLY);
  2306       (*Globals[GlobalIndex].ResizeFunc) (NewNum);
  2307       NewNum = 0;
  2308       for (list = StartList; list; NewNum++, list = g_slist_next(list)) {
  2309         AssignName(&(*(Globals[GlobalIndex].StringList))[NewNum],
  2310                    (char *)list->data);
  2311         g_free(list->data);
  2312       }
  2313       g_slist_free(StartList);
  2314     }
  2315   }
  2316   if (Globals[GlobalIndex].NameStruct[0])
  2317     g_free(GlobalName);
  2318 
  2319   Globals[GlobalIndex].Modified = TRUE;
  2320   return TRUE;
  2321 }
  2322 
  2323 /*
  2324  * Returns the URL of the directory containing local HTML documentation.
  2325  */
  2326 gchar *GetDocRoot(void)
  2327 {
  2328   gchar *path;
  2329 #ifdef CYGWIN
  2330   gchar *bindir;
  2331 
  2332   bindir = GetBinaryDir();
  2333   path = g_strdup_printf("file://%s/doc/", bindir);
  2334   g_free(bindir);
  2335 #else
  2336   path = g_strdup_printf("file://%s/", DPDOCDIR);
  2337 #endif
  2338   return path;
  2339 }
  2340 
  2341 /*
  2342  * Returns the URL of the index file for the local HTML documentation.
  2343  */
  2344 gchar *GetDocIndex(void)
  2345 {
  2346   gchar *file, *root;
  2347 
  2348   root = GetDocRoot();
  2349   file = g_strdup_printf("%sindex.html", root);
  2350   g_free(root);
  2351   return file;
  2352 }
  2353 
  2354 #ifdef CYGWIN
  2355 extern gchar *appdata_path;
  2356 #endif
  2357 
  2358 /*
  2359  * Returns the pathname of the global (all users) configuration file,
  2360  * as a dynamically-allocated string that must be later freed. On
  2361  * error, NULL is returned.
  2362  */
  2363 gchar *GetGlobalConfigFile(void)
  2364 {
  2365 #ifdef CYGWIN
  2366   gchar *bindir, *conf = NULL;
  2367 
  2368   /* Global configuration is in the same directory as the dopewars binary */
  2369   bindir = GetBinaryDir();
  2370   if (bindir) {
  2371     conf = g_strdup_printf("%s/dopewars-config.txt", bindir);
  2372     g_free(bindir);
  2373   }
  2374   return conf;
  2375 #else
  2376   return g_strdup("/etc/dopewars");
  2377 #endif
  2378 }
  2379 
  2380 /*
  2381  * Returns the pathname of the local (per-user) configuration file,
  2382  * as a dynamically-allocated string that must be later freed. On
  2383  * error, NULL is returned.
  2384  */
  2385 gchar *GetLocalConfigFile(void)
  2386 {
  2387 #ifdef CYGWIN
  2388   return g_strdup_printf("%s/dopewars-config.txt",
  2389                          appdata_path ? appdata_path : ".");
  2390 #else
  2391   gchar *home, *conf = NULL;
  2392 
  2393   /* Local config is in the user's home directory */
  2394   home = getenv("HOME");
  2395   if (home) {
  2396     conf = g_strdup_printf("%s/.dopewars", home);
  2397   }
  2398   return conf;
  2399 #endif
  2400 }
  2401 
  2402 /* 
  2403  * Sets up data - such as the location of the high score file - to
  2404  * hard-coded internal values, and then processes the global and
  2405  * user-specific configuration files.
  2406  */
  2407 static void SetupParameters(GSList *extraconfigs, gboolean antique)
  2408 {
  2409   gchar *conf;
  2410   GSList *list;
  2411   int i, defloc;
  2412 
  2413   DrugValue = TRUE;
  2414   Sanitized = ConfigVerbose = FALSE;
  2415 
  2416   g_free(Currency.Symbol);
  2417   /* The currency symbol */
  2418   Currency.Symbol = g_strdup(_("$"));
  2419   Currency.Prefix = (strcmp("Currency.Prefix=TRUE",
  2420   /* Translate this to "Currency.Prefix=FALSE" if you want your currency
  2421      symbol to follow all prices. */
  2422                             _("Currency.Prefix=TRUE")) == 0);
  2423 
  2424   /* Set hard-coded default values */
  2425   AssignName(&ServerName, "localhost");
  2426   AssignName(&ServerMOTD, "");
  2427   AssignName(&BindAddress, "");
  2428   AssignName(&OurWebBrowser, "/usr/bin/firefox");
  2429 
  2430   AssignName(&Sounds.FightHit, SNDPATH"colt.wav");
  2431   AssignName(&Sounds.EnemyBitchKilled, SNDPATH"shotdown.wav");
  2432   AssignName(&Sounds.BitchKilled, SNDPATH"losebitch.wav");
  2433   AssignName(&Sounds.EnemyKilled, SNDPATH"shotdown.wav");
  2434   AssignName(&Sounds.Killed, SNDPATH"die.wav");
  2435   AssignName(&Sounds.EnemyFlee, SNDPATH"run.wav");
  2436   AssignName(&Sounds.Flee, SNDPATH"run.wav");
  2437   AssignName(&Sounds.Jet, SNDPATH"train.wav");
  2438   AssignName(&Sounds.TalkPrivate, SNDPATH"murmur.wav");
  2439   AssignName(&Sounds.TalkToAll, SNDPATH"message.wav");
  2440   AssignName(&Sounds.EndGame, SNDPATH"bye.wav");
  2441 
  2442   LoanSharkLoc = DEFLOANSHARK;
  2443   BankLoc = DEFBANK;
  2444   if (antique) {
  2445     GunShopLoc = RoughPubLoc = 0;
  2446   } else {
  2447     GunShopLoc = DEFGUNSHOP;
  2448     RoughPubLoc = DEFROUGHPUB;
  2449   }
  2450 
  2451   CopyNames(&Names, &DefaultNames);
  2452   CopyDrugs(&Drugs, &DefaultDrugs);
  2453 
  2454 #ifdef NETWORKING
  2455   CopyMetaServer(&MetaServer, &DefaultMetaServer);
  2456   AssignName(&Socks.name, "socks");
  2457   Socks.port = 1080;
  2458   Socks.version = 4;
  2459   g_free(Socks.user);
  2460   g_free(Socks.authuser);
  2461   g_free(Socks.authpassword);
  2462   Socks.user = g_strdup("");
  2463   Socks.numuid = FALSE;
  2464   Socks.authuser = g_strdup("");
  2465   Socks.authpassword = g_strdup("");
  2466   UseSocks = FALSE;
  2467 #endif
  2468 
  2469   defloc = sizeof(DefaultLocation) / sizeof(DefaultLocation[0]);
  2470   g_assert(defloc >= 6);
  2471   if (antique) {
  2472     defloc = 6;
  2473   }
  2474   ResizeLocations(defloc);
  2475   for (i = 0; i < NumLocation; i++)
  2476     CopyLocation(&Location[i], &DefaultLocation[i]);
  2477   ResizeCops(sizeof(DefaultCop) / sizeof(DefaultCop[0]));
  2478   for (i = 0; i < NumCop; i++)
  2479     CopyCop(&Cop[i], &DefaultCop[i]);
  2480   ResizeGuns(sizeof(DefaultGun) / sizeof(DefaultGun[0]));
  2481   for (i = 0; i < NumGun; i++)
  2482     CopyGun(&Gun[i], &DefaultGun[i]);
  2483   ResizeDrugs(sizeof(DefaultDrug) / sizeof(DefaultDrug[0]));
  2484   for (i = 0; i < NumDrug; i++)
  2485     CopyDrug(&Drug[i], &DefaultDrug[i]);
  2486   ResizeSubway(sizeof(DefaultSubwaySaying) /
  2487                sizeof(DefaultSubwaySaying[0]));
  2488   for (i = 0; i < NumSubway; i++) {
  2489     AssignName(&SubwaySaying[i], _(DefaultSubwaySaying[i]));
  2490   }
  2491   ResizePlaying(sizeof(DefaultPlaying) / sizeof(DefaultPlaying[0]));
  2492   for (i = 0; i < NumPlaying; i++) {
  2493     AssignName(&Playing[i], _(DefaultPlaying[i]));
  2494   }
  2495   ResizeStoppedTo(sizeof(DefaultStoppedTo) / sizeof(DefaultStoppedTo[0]));
  2496   for (i = 0; i < NumStoppedTo; i++) {
  2497     AssignName(&StoppedTo[i], _(DefaultStoppedTo[i]));
  2498   }
  2499 
  2500   /* Replace nasty null pointers with null strings */
  2501   for (i = 0; i < NUMGLOB; ++i) {
  2502     if (Globals[i].StringVal && !*Globals[i].StringVal) {
  2503       *Globals[i].StringVal = g_strdup("");
  2504     }
  2505   }
  2506 
  2507   /* Now read in the global configuration file */
  2508   conf = GetGlobalConfigFile();
  2509   if (conf) {
  2510     ReadConfigFile(conf, NULL);
  2511     g_free(conf);
  2512   }
  2513 
  2514   /* Next, try the local configuration file */
  2515   conf = GetLocalConfigFile();
  2516   if (conf) {
  2517     ReadConfigFile(conf, &LocalCfgEncoding);
  2518     g_free(conf);
  2519   }
  2520 
  2521   /* Finally, any configuration files named on the command line */
  2522   for (list = extraconfigs; list; list = g_slist_next(list)) {
  2523     ReadConfigFile(list->data, NULL);
  2524   }
  2525 }
  2526 
  2527 void GetDateString(GString *str, Player *Play)
  2528 {
  2529   gchar buf[200], *turn, *pt;
  2530 
  2531   turn = g_strdup_printf("%d", Play->Turn);
  2532   g_string_assign(str, Names.Date);
  2533   while ((pt = strstr(str->str, "%T")) != NULL) {
  2534     int ind = pt - str->str;
  2535 
  2536     g_string_erase(str, ind, 2);
  2537     g_string_insert(str, ind, turn);
  2538   }
  2539 
  2540   g_date_strftime(buf, sizeof(buf), str->str, Play->date);
  2541   g_string_assign(str, buf);
  2542   g_free(turn);
  2543 }
  2544 
  2545 static void PluginHelp(void)
  2546 {
  2547   gchar *plugins;
  2548 #ifdef HAVE_GETOPT_LONG
  2549   g_print(_("  -u, --plugin=FILE       use sound plugin \"FILE\"\n"
  2550             "                            "));
  2551 #else
  2552   g_print(_("  -u file  use sound plugin \"file\"\n"
  2553             "                  "));
  2554 #endif
  2555   plugins = GetPluginList();
  2556   g_print(_("(%s available)\n"), plugins);
  2557   g_free(plugins);
  2558 }
  2559 
  2560 void HandleHelpTexts(gboolean fullhelp)
  2561 {
  2562   g_print(_("dopewars version %s\n"), VERSION);
  2563   if (!fullhelp) {
  2564     return;
  2565   }
  2566 
  2567   g_print(
  2568 #ifdef HAVE_GETOPT_LONG
  2569            /* Usage information, printed when the user runs "dopewars -h"
  2570               (version with support for GNU long options) */
  2571            _("Usage: dopewars [OPTION]...\n\
  2572 Drug dealing game based on \"Drug Wars\" by John E. Dell\n\
  2573   -b, --no-color,         \"black and white\" - i.e. do not use pretty colors\n\
  2574       --no-colour           (by default colors are used where available)\n\
  2575   -n, --single-player     be boring and don't connect to any available dopewars\n\
  2576                             servers (i.e. single player mode)\n\
  2577   -a, --antique           \"antique\" dopewars - keep as closely to the original\n\
  2578                             version as possible (no networking)\n\
  2579   -f, --scorefile=FILE    specify a file to use as the high score table (by\n\
  2580                             default %s/dopewars.sco is used)\n\
  2581   -o, --hostname=ADDR     specify a hostname where the server for multiplayer\n\
  2582                             dopewars can be found\n\
  2583   -s, --public-server     run in server mode (note: see the -A option for\n\
  2584                             configuring a server once it\'s running)\n\
  2585   -S, --private-server    run a \"private\" server (do not notify the metaserver)\n\
  2586   -p, --port=PORT         specify the network port to use (default: 7902)\n\
  2587   -g, --config-file=FILE  specify the pathname of a dopewars configuration file;\n\
  2588                             this file is read immediately when the -g option\n\
  2589                             is encountered\n\
  2590   -r, --pidfile=FILE      maintain pid file \"FILE\" while running the server\n\
  2591   -l, --logfile=FILE      write log information to \"FILE\"\n\
  2592   -A, --admin             connect to a locally-running server for administration\n\
  2593   -c, --ai-player         create and run a computer player\n\
  2594   -w, --windowed-client   force the use of a graphical (windowed)\n\
  2595                             client (GTK+ or Win32)\n\
  2596   -t, --text-client       force the use of a text-mode client (curses) (by\n\
  2597                             default, a windowed client is used when possible)\n\
  2598   -P, --player=NAME       set player name to \"NAME\"\n\
  2599   -C, --convert=FILE      convert an \"old format\" score file to the new format\n"), DPSCOREDIR);
  2600   PluginHelp();
  2601   g_print(_("  -h, --help              display this help information\n\
  2602   -v, --version           output version information and exit\n\n\
  2603 dopewars is Copyright (C) Ben Webb 1998-2021, and released under the GNU GPL\n\
  2604 Report bugs to the author at benwebb@users.sf.net\n"));
  2605 #else
  2606            /* Usage information, printed when the user runs "dopewars -h"
  2607               (short options only version) */
  2608            _("Usage: dopewars [OPTION]...\n\
  2609 Drug dealing game based on \"Drug Wars\" by John E. Dell\n\
  2610   -b       \"black and white\" - i.e. do not use pretty colors\n\
  2611               (by default colors are used where the terminal supports them)\n\
  2612   -n       be boring and don't connect to any available dopewars servers\n\
  2613               (i.e. single player mode)\n\
  2614   -a       \"antique\" dopewars - keep as closely to the original version as\n\
  2615               possible (no networking)\n\
  2616   -f file  specify a file to use as the high score table\n\
  2617               (by default %s/dopewars.sco is used)\n\
  2618   -o addr  specify a hostname where the server for multiplayer dopewars\n\
  2619               can be found\n\
  2620   -s       run in server mode (note: see the -A option for configuring a\n\
  2621               server once it\'s running)\n\
  2622   -S       run a \"private\" server (i.e. do not notify the metaserver)\n\
  2623   -p port  specify the network port to use (default: 7902)\n\
  2624   -g file  specify the pathname of a dopewars configuration file; this file\n\
  2625               is read immediately when the -g option is encountered\n\
  2626   -r file  maintain pid file \"file\" while running the server\n\
  2627   -l file  write log information to \"file\"\n\
  2628   -c       create and run a computer player\n\
  2629   -w       force the use of a graphical (windowed) client (GTK+ or Win32)\n\
  2630   -t       force the use of a text-mode client (curses)\n\
  2631               (by default, a windowed client is used when possible)\n\
  2632   -P name  set player name to \"name\"\n\
  2633   -C file  convert an \"old format\" score file to the new format\n\
  2634   -A       connect to a locally-running server for administration\n"),
  2635            DPSCOREDIR);
  2636   PluginHelp();
  2637 g_print(_("  -h       display this help information\n\
  2638   -v       output version information and exit\n\n\
  2639 dopewars is Copyright (C) Ben Webb 1998-2021, and released under the GNU GPL\n\
  2640 Report bugs to the author at benwebb@users.sf.net\n"));
  2641 #endif
  2642 }
  2643 
  2644 struct CMDLINE *ParseCmdLine(int argc, char *argv[])
  2645 {
  2646   int c;
  2647   struct CMDLINE *cmdline = g_new0(struct CMDLINE, 1);
  2648   static const gchar *options = "anbchvf:o:sSp:g:r:wtC:l:NAu:P:";
  2649 
  2650 #ifdef HAVE_GETOPT_LONG
  2651   static const struct option long_options[] = {
  2652     {"no-color", no_argument, NULL, 'b'},
  2653     {"no-colour", no_argument, NULL, 'b'},
  2654     {"single-player", no_argument, NULL, 'n'},
  2655     {"antique", no_argument, NULL, 'a'},
  2656     {"scorefile", required_argument, NULL, 'f'},
  2657     {"hostname", required_argument, NULL, 'o'},
  2658     {"public-server", no_argument, NULL, 's'},
  2659     {"private-server", no_argument, NULL, 'S'},
  2660     {"port", required_argument, NULL, 'p'},
  2661     {"configfile", required_argument, NULL, 'g'},
  2662     {"pidfile", required_argument, NULL, 'r'},
  2663     {"ai-player", no_argument, NULL, 'c'},
  2664     {"windowed-client", no_argument, NULL, 'w'},
  2665     {"text-client", no_argument, NULL, 't'},
  2666     {"player", required_argument, NULL, 'P'},
  2667     {"convert", required_argument, NULL, 'C'},
  2668     {"logfile", required_argument, NULL, 'l'},
  2669     {"admin", no_argument, NULL, 'A'},
  2670     {"plugin", required_argument, NULL, 'u'},
  2671     {"help", no_argument, NULL, 'h'},
  2672     {"version", no_argument, NULL, 'v'},
  2673     {0, 0, 0, 0}
  2674   };
  2675 #endif
  2676 
  2677   cmdline->scorefile = cmdline->servername = cmdline->pidfile
  2678       = cmdline->logfile = cmdline->plugin = cmdline->convertfile
  2679       = cmdline->playername = NULL;
  2680   cmdline->configs = NULL;
  2681   cmdline->color = cmdline->network = TRUE;
  2682   cmdline->client = CLIENT_AUTO;
  2683 
  2684   do {
  2685 #ifdef HAVE_GETOPT_LONG
  2686     c = getopt_long(argc, argv, options, long_options, NULL);
  2687 #else
  2688     c = getopt(argc, argv, options);
  2689 #endif
  2690     switch (c) {
  2691     case 'n':
  2692       cmdline->network = FALSE;
  2693       break;
  2694     case 'b':
  2695       cmdline->color = FALSE;
  2696       break;
  2697     case 'c':
  2698       cmdline->ai = TRUE;
  2699       break;
  2700     case 'a':
  2701       cmdline->antique = TRUE;
  2702       cmdline->network = FALSE;
  2703       break;
  2704     case 'v':
  2705       cmdline->version = TRUE;
  2706       break;
  2707     case 'h':
  2708     case 0:
  2709     case '?':
  2710       cmdline->help = TRUE;
  2711       break;
  2712     case 'f':
  2713       AssignName(&cmdline->scorefile, optarg);
  2714       break;
  2715     case 'o':
  2716       AssignName(&cmdline->servername, optarg);
  2717       break;
  2718     case 's':
  2719       cmdline->server = TRUE;
  2720       cmdline->notifymeta = TRUE;
  2721       break;
  2722     case 'S':
  2723       cmdline->server = TRUE;
  2724       cmdline->notifymeta = FALSE;
  2725       break;
  2726     case 'p':
  2727       cmdline->setport = TRUE;
  2728       cmdline->port = atoi(optarg);
  2729       break;
  2730     case 'g':
  2731       cmdline->configs = g_slist_append(cmdline->configs, g_strdup(optarg));
  2732       break;
  2733     case 'r':
  2734       AssignName(&cmdline->pidfile, optarg);
  2735       break;
  2736     case 'l':
  2737       AssignName(&cmdline->logfile, optarg);
  2738       break;
  2739     case 'u':
  2740       AssignName(&cmdline->plugin, optarg);
  2741       break;
  2742     case 'w':
  2743       cmdline->client = CLIENT_WINDOW;
  2744       break;
  2745     case 't':
  2746       cmdline->client = CLIENT_CURSES;
  2747       break;
  2748     case 'P':
  2749       AssignName(&cmdline->playername, optarg);
  2750       break;
  2751     case 'C':
  2752       AssignName(&cmdline->convertfile, optarg);
  2753       cmdline->convert = TRUE;
  2754       break;
  2755     case 'A':
  2756       cmdline->admin = TRUE;
  2757       break;
  2758     }
  2759   } while (c != -1);
  2760   cmdline->color = FALSE;
  2761 
  2762   return cmdline;
  2763 }
  2764 
  2765 void FreeCmdLine(struct CMDLINE *cmdline)
  2766 {
  2767   GSList *list;
  2768 
  2769   g_free(cmdline->scorefile);
  2770   g_free(cmdline->servername);
  2771   g_free(cmdline->pidfile);
  2772   g_free(cmdline->logfile);
  2773   g_free(cmdline->plugin);
  2774   g_free(cmdline->convertfile);
  2775   g_free(cmdline->playername);
  2776 
  2777   for (list = cmdline->configs; list; list = g_slist_next(list)) {
  2778     g_free(list->data);
  2779   }
  2780   g_slist_free(list);
  2781   g_free(cmdline);
  2782 }
  2783 
  2784 static gchar *priv_hiscore = NULL;
  2785 
  2786 /* 
  2787  * Does general startup stuff (command line, dropping privileges,
  2788  * and high score init; config files are handled later)
  2789  */
  2790 struct CMDLINE *GeneralStartup(int argc, char *argv[])
  2791 {
  2792   /* First, open the hard-coded high score file with possibly
  2793    * elevated privileges */
  2794 #ifdef CYGWIN
  2795   priv_hiscore = g_strdup_printf("%s/dopewars.sco",
  2796                                  appdata_path ? appdata_path : DPSCOREDIR);
  2797 #else
  2798   priv_hiscore = g_strdup_printf("%s/dopewars.sco", DPSCOREDIR);
  2799 #endif
  2800   HiScoreFile = g_strdup(priv_hiscore);
  2801   OpenHighScoreFile();
  2802   DropPrivileges();
  2803 
  2804   /* Initialize variables */
  2805   Log.File = g_strdup("");
  2806   Log.Level = 2;
  2807   Log.Timestamp = g_strdup("[%H:%M:%S] ");
  2808   srand((unsigned)time(NULL));
  2809   Noone.Name = g_strdup("Noone");
  2810   Server = Client = Network = FALSE;
  2811 
  2812   return ParseCmdLine(argc, argv);
  2813 }
  2814 
  2815 void InitConfiguration(struct CMDLINE *cmdline)
  2816 {
  2817   ConfigErrors = 0;
  2818   SetupParameters(cmdline->configs, cmdline->antique);
  2819 
  2820   if (cmdline->scorefile) {
  2821     AssignName(&HiScoreFile, cmdline->scorefile);
  2822   }
  2823   if (cmdline->servername) {
  2824     AssignName(&ServerName, cmdline->servername);
  2825   }
  2826   if (cmdline->playername) {
  2827     AssignName(&PlayerName, cmdline->playername);
  2828   }
  2829   if (cmdline->pidfile) {
  2830     AssignName(&PidFile, cmdline->pidfile);
  2831   }
  2832   if (cmdline->logfile) {
  2833     AssignName(&Log.File, cmdline->logfile);
  2834     OpenLog();
  2835   }
  2836   if (cmdline->setport) {
  2837     Port = cmdline->port;
  2838   }
  2839 #ifdef NETWORKING
  2840   if (cmdline->server) {
  2841     MetaServer.Active = cmdline->notifymeta;
  2842   }
  2843 #endif
  2844   WantAntique = cmdline->antique;
  2845 
  2846   if (!cmdline->version && !cmdline->help && !cmdline->ai
  2847       && !cmdline->convert && !cmdline->admin) {
  2848     /* Open a user-specified high score file with no privileges, if one
  2849      * was given */
  2850     if (strcmp(priv_hiscore, HiScoreFile) != 0) {
  2851       CloseHighScoreFile();
  2852       OpenHighScoreFile();
  2853     }
  2854   } else {
  2855     CloseHighScoreFile();
  2856   }
  2857 }
  2858 
  2859 /*
  2860  * Removes any ^ or \n characters from the given string, which is
  2861  * modified in place.
  2862  */
  2863 void StripTerminators(gchar *str)
  2864 {
  2865   guint i;
  2866 
  2867   if (str) {
  2868     for (i = 0; i < strlen(str); i++) {
  2869       switch(str[i]) {
  2870       case '^':
  2871       case '\n':
  2872         str[i] = '~';
  2873         break;
  2874       default:
  2875         break;
  2876       }
  2877     }
  2878   }
  2879 }
  2880 
  2881 #ifndef CYGWIN
  2882 
  2883 #if defined(NETWORKING) && !defined(GUI_SERVER)
  2884 static void ServerLogMessage(const gchar *log_domain,
  2885                              GLogLevelFlags log_level,
  2886                              const gchar *message, gpointer user_data)
  2887 {
  2888   GString *text;
  2889 
  2890   text = GetLogString(log_level, message);
  2891   if (text) {
  2892     fprintf(Log.fp ? Log.fp : stdout, "%s\n", text->str);
  2893     g_string_free(text, TRUE);
  2894   }
  2895 }
  2896 #endif
  2897 
  2898 #ifndef CURSES_CLIENT
  2899 /*
  2900  * Stub function to report an error if the Curses client is requested and
  2901  * it isn't compiled in.
  2902  */
  2903 void CursesLoop(struct CMDLINE *cmdline)
  2904 {
  2905   g_print(_("No curses client available - rebuild the binary passing the\n"
  2906             "--enable-curses-client option to configure, or use a windowed\n"
  2907             "client (if available) instead!\n"));
  2908 }
  2909 #endif
  2910 
  2911 #ifndef GUI_CLIENT
  2912 /*
  2913  * Stub function to report an error if the GTK+ client is requested and
  2914  * it isn't compiled in.
  2915  */
  2916 #ifdef CYGWIN
  2917 gboolean GtkLoop(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  2918                  struct CMDLINE *cmdline, gboolean ReturnOnFail)
  2919 #else
  2920 gboolean GtkLoop(int *argc, char **argv[], struct CMDLINE *cmdline,
  2921                  gboolean ReturnOnFail)
  2922 #endif
  2923 {
  2924   if (!ReturnOnFail) {
  2925     g_print(_("No graphical client available - rebuild the binary\n"
  2926               "passing the --enable-gui-client option to configure, or\n"
  2927               "use the curses client (if available) instead!\n"));
  2928   }
  2929   return FALSE;
  2930 }
  2931 #endif
  2932 
  2933 static void DefaultLogMessage(const gchar *log_domain,
  2934                               GLogLevelFlags log_level,
  2935                               const gchar *message, gpointer user_data)
  2936 {
  2937   GString *text;
  2938 
  2939   text = GetLogString(log_level, message);
  2940   if (text) {
  2941     g_print("%s\n", text->str);
  2942     g_string_free(text, TRUE);
  2943   }
  2944 }
  2945 
  2946 /* 
  2947  * Standard program entry - Win32 uses WinMain() instead, in winmain.c
  2948  */
  2949 int main(int argc, char *argv[])
  2950 {
  2951   struct CMDLINE *cmdline;
  2952 #ifdef ENABLE_NLS
  2953   const char *charset;
  2954   setlocale(LC_ALL, "");
  2955   bindtextdomain(PACKAGE, LOCALEDIR);
  2956   textdomain(PACKAGE);
  2957   LocaleIsUTF8 = g_get_charset(&charset);
  2958 #endif
  2959   WantUTF8Errors(FALSE);
  2960   g_log_set_handler(NULL, LogMask(), DefaultLogMessage, NULL);
  2961   cmdline = GeneralStartup(argc, argv);
  2962   if (cmdline->logfile) {
  2963     AssignName(&Log.File, cmdline->logfile);
  2964   }
  2965   OpenLog();
  2966   SoundInit();
  2967   if (cmdline->version || cmdline->help) {
  2968     HandleHelpTexts(cmdline->help);
  2969   } else if (cmdline->admin) {
  2970 #ifdef NETWORKING
  2971     AdminServer(cmdline);
  2972 #else
  2973     g_print(_("This binary has been compiled without networking "
  2974               "support, and thus cannot run\nin admin mode. "
  2975               "Recompile passing --enable-networking to the "
  2976               "configure script.\n"));
  2977 #endif
  2978   } else if (cmdline->convert) {
  2979     ConvertHighScoreFile(cmdline->convertfile);
  2980   } else {
  2981     InitNetwork();
  2982     if (cmdline->server) {
  2983 #ifdef NETWORKING
  2984 #ifdef GUI_SERVER
  2985       Server = TRUE;
  2986       gtk_set_locale();
  2987       gtk_init(&argc, &argv);
  2988       GuiServerLoop(cmdline, FALSE);
  2989 #else
  2990       g_log_set_handler(NULL, LogMask(), ServerLogMessage, NULL);
  2991       ServerLoop(cmdline);
  2992 #endif /* GUI_SERVER */
  2993 #else
  2994       g_print(_("This binary has been compiled without networking "
  2995                 "support, and thus cannot run\nin server mode. "
  2996                 "Recompile passing --enable-networking to the "
  2997                 "configure script.\n"));
  2998 #endif /* NETWORKING */
  2999     } else if (cmdline->ai) {
  3000       AIPlayerLoop(cmdline);
  3001     } else
  3002       switch (cmdline->client) {
  3003       case CLIENT_AUTO:
  3004         if (!GtkLoop(&argc, &argv, cmdline, TRUE))
  3005           CursesLoop(cmdline);
  3006         break;
  3007       case CLIENT_WINDOW:
  3008         GtkLoop(&argc, &argv, cmdline, FALSE);
  3009         break;
  3010       case CLIENT_CURSES:
  3011         CursesLoop(cmdline);
  3012         break;
  3013       }
  3014 #ifdef NETWORKING
  3015     StopNetworking();
  3016 #endif
  3017   }
  3018   FreeCmdLine(cmdline);
  3019   CloseLog();
  3020   CloseHighScoreFile();
  3021   g_free(PidFile);
  3022   g_free(Log.File);
  3023   SoundClose();
  3024   return 0;
  3025 }
  3026 
  3027 #endif /* CYGWIN */