Crossfire Client, Branch  R11627
client.c
Go to the documentation of this file.
00001 const char * const rcsid_common_client_c =
00002     "$Id: client.c 9215 2008-06-02 18:31:04Z anmaster $";
00003 /*
00004     Crossfire client, a client program for the crossfire program.
00005 
00006     Copyright (C) 2001 Mark Wedel & Crossfire Development Team
00007 
00008     This program is free software; you can redistribute it and/or modify
00009     it under the terms of the GNU General Public License as published by
00010     the Free Software Foundation; either version 2 of the License, or
00011     (at your option) any later version.
00012 
00013     This program is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016     GNU General Public License for more details.
00017 
00018     You should have received a copy of the GNU General Public License
00019     along with this program; if not, write to the Free Software
00020     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00021 
00022     The author can be reached via e-mail to crossfire-devel@real-time.com
00023 */
00024 
00025  /* Client interface main routine.
00026   * this file sets up a few global variables, connects to the server,
00027   * tells it what kind of pictures it wants, adds the client and enters
00028   * the main dispatch loop
00029   *
00030   * the main event loop (event_loop()) checks the tcp socket for input and
00031   * then polls for x events.  This should be fixed since you can just block
00032   * on both filedescriptors.
00033   *
00034   * The DoClient function recieves a message (an ArgList), unpacks it, and
00035   * in a slow for loop dispatches the command to the right function through
00036   * the commands table.   ArgLists are essentially like RPC things, only
00037   * they don't require going through RPCgen, and it's easy to get variable
00038   * length lists.  They are just lists of longs, strings, characters, and
00039   * byte arrays that can be converted to a machine independent format
00040  */
00041 
00042 
00043 #include <client.h>
00044 #include <external.h>
00045 #include <errno.h>
00046 #include <script.h>
00047 #include <ctype.h>
00048 
00049 #include "mapdata.h"
00050 
00051 /* actually declare the globals */
00052 
00053 #ifdef SERVER
00054 char *server=SERVER;
00055 #else
00056 char *server=NULL;
00057 #endif
00058 
00059 char VERSION_INFO[256];
00060 
00061 char *client_libdir=NULL,*meta_server=META_SERVER;
00062 char *sound_server="cfsndserv";
00063 char *skill_names[MAX_SKILL];
00064 
00065 int last_used_skills[MAX_SKILL+1];
00066 
00067 int meta_port=META_PORT, want_skill_exp=0,
00068     replyinfo_status=0, requestinfo_sent=0, replyinfo_last_face=0,
00069     maxfd,metaserver_on=METASERVER, metaserver2_on=METASERVER2;
00070 uint32  tick=0;
00071 
00072 uint16  exp_table_max=0;
00073 uint64  *exp_table=NULL;
00074 
00075 int command_inscribe = 0;
00076 
00077 Client_Player cpl;
00078 ClientSocket csocket;
00079 
00080 const char *const resists_name[NUM_RESISTS] = {
00081 "armor", "magic", "fire", "elec",
00082 "cold", "conf", "acid", "drain",
00083 "ghit", "pois", "slow", "para",
00084 "t undead", "fear", "depl","death",
00085 "hword", "blind"};
00086 
00087 typedef void (*CmdProc)(unsigned char *, int len);
00088 
00089 struct CmdMapping {
00090   const char *cmdname;
00091   void (*cmdproc)(unsigned char *, int );
00092   enum CmdFormat cmdformat;
00093 };
00094 
00095 
00096 struct CmdMapping commands[] = {
00097     /* Order of this table doesn't make a difference.  I tried to sort
00098      * of cluster the related stuff together.
00099      */
00100     { "map2", Map2Cmd, SHORT_ARRAY },
00101     { "map_scroll", (CmdProc)map_scrollCmd, ASCII },
00102     { "magicmap", MagicMapCmd, MIXED /* ASCII, then binary */},
00103     { "newmap", NewmapCmd, NODATA },
00104     { "mapextended", MapExtendedCmd, MIXED /* chars, then SHORT_ARRAY */ },
00105 
00106     { "item2", Item2Cmd, MIXED },
00107     { "upditem", UpdateItemCmd, MIXED },
00108     { "delitem", DeleteItem, INT_ARRAY },
00109     { "delinv", DeleteInventory, ASCII },
00110 
00111     { "addspell", AddspellCmd, MIXED },
00112     { "updspell", UpdspellCmd, MIXED },
00113     { "delspell", DeleteSpell, INT_ARRAY },
00114 
00115     { "drawinfo", (CmdProc)DrawInfoCmd, ASCII },
00116     { "drawextinfo", (CmdProc)DrawExtInfoCmd, ASCII},
00117     { "stats", StatsCmd, STATS /* array of: int8, (int?s for that stat) */ },
00118 
00119     { "image2", Image2Cmd, MIXED /* int, int8, int, PNG */ },
00120     { "face2", Face2Cmd, MIXED /* int16, int8, int32, string */},
00121     { "tick", TickCmd, INT_ARRAY /* uint32 */},
00122 
00123 
00124     { "sound", SoundCmd, MIXED /* int8, int8, int16, int8 */},
00125     { "sound2", Sound2Cmd, MIXED /* int8, int8, int8, int8, int8, int8, chars, int8, chars */},
00126     { "music", (CmdProc)MusicCmd, ASCII },
00127     { "anim", AnimCmd, SHORT_ARRAY},
00128     { "smooth", SmoothCmd, SHORT_ARRAY},
00129 
00130     { "player", PlayerCmd, MIXED /* 3 ints, int8, str */ },
00131     { "comc", CompleteCmd, SHORT_INT },
00132 
00133     { "addme_failed", (CmdProc)AddMeFail, NODATA },
00134     { "addme_success", (CmdProc)AddMeSuccess, NODATA },
00135     { "version", (CmdProc)VersionCmd, ASCII },
00136     { "goodbye", (CmdProc)GoodbyeCmd, NODATA },
00137     { "setup", (CmdProc)SetupCmd, ASCII},
00138     { "ExtendedInfoSet", (CmdProc)ExtendedInfoSetCmd, NODATA},
00139 
00140     { "query", (CmdProc)handle_query, ASCII},
00141     { "replyinfo", ReplyInfoCmd, ASCII},
00142     { "ExtendedTextSet", (CmdProc)SinkCmd, NODATA},
00143 
00144     { "pickup", PickupCmd, INT_ARRAY /* uint32 */},
00145 };
00146 
00147 #define NCOMMANDS ((int)(sizeof(commands)/sizeof(struct CmdMapping)))
00148 
00149 void DoClient(ClientSocket *csocket)
00150 {
00151     int i,len;
00152     unsigned char *data;
00153 
00154     while (1) {
00155         i=SockList_ReadPacket(csocket->fd, &csocket->inbuf, MAXSOCKBUF-1);
00156         if (i==-1) {
00157             /* Need to add some better logic here */
00158 #ifdef WIN32
00159             closesocket(csocket->fd);
00160 #else
00161             close(csocket->fd);
00162 #endif
00163             csocket->fd=-1;
00164             return;
00165         }
00166         if (i==0) return;   /* Don't have a full packet */
00167         /* Terminate the buffer */
00168         csocket->inbuf.buf[csocket->inbuf.len]='\0';
00169         data = csocket->inbuf.buf+2;
00170         while ((*data != ' ') && (*data != '\0')) ++data;
00171         if (*data == ' ') {
00172             *data='\0';
00173             data++;
00174             len = csocket->inbuf.len - (data - csocket->inbuf.buf);
00175         }
00176         else len = 0;
00177         for(i=0;i < NCOMMANDS;i++) {
00178             if (strcmp((char*)csocket->inbuf.buf+2,commands[i].cmdname)==0) {
00179                     script_watch((char*)csocket->inbuf.buf+2,data,len,commands[i].cmdformat);
00180                     commands[i].cmdproc(data,len);
00181                     break;
00182             }
00183         }
00184         csocket->inbuf.len=0;
00185         if (i == NCOMMANDS) {
00186             printf("Bad command from server (%s)\n",csocket->inbuf.buf+2);
00187         }
00188     }
00189 }
00190 
00191 #ifdef WIN32
00192 #define socklen_t int
00193 #else
00194 #include <netdb.h>
00195 #include <sys/types.h>
00196 #include <sys/socket.h>
00197 #include <netinet/in.h>
00198 #include <netinet/tcp.h>
00199 #include <ctype.h>
00200 #include <arpa/inet.h>
00201 #endif
00202 
00203 /* returns the fd of the connected socket, -1 on failure. */
00204 
00205 int init_connection(char *host, int port)
00206 {
00207     int fd = -1, oldbufsize, newbufsize=65535;
00208     socklen_t buflen=sizeof(int);
00209 #if !HAVE_GETADDRINFO || WIN32
00210     struct sockaddr_in insock;
00211     struct protoent *protox;
00212 
00213     /* In my cases, an empty host will be saved as (null) in
00214      * the defaults file.  However, upon loading, that doesn't
00215      * show up as a NULL, but rather this string.  For whatever
00216      * reasons, at least on my system, the lookup of this takes
00217      * a long time, and it isn't a valid host name in any case,
00218      * so just abort quickly
00219      */
00220     if (!strcmp(host,"(null)")) return -1;
00221 
00222     protox = getprotobyname("tcp");
00223     if (protox == (struct protoent  *) NULL)
00224     {
00225         LOG (LOG_ERROR,"common::init_connection", "Error getting protobyname (tcp)");
00226         return -1;
00227     }
00228     fd = socket(PF_INET, SOCK_STREAM, protox->p_proto);
00229     if (fd==-1) {
00230             perror("init_connection:  Error on socket command.\n");
00231             LOG (LOG_ERROR,"common::init_connection", "Error on socket command");
00232         return -1;
00233     }
00234     insock.sin_family = AF_INET;
00235     insock.sin_port = htons((unsigned short)port);
00236     if (isdigit(*host))
00237         insock.sin_addr.s_addr = inet_addr(host);
00238     else {
00239         struct hostent *hostbn = gethostbyname(host);
00240         if (hostbn == (struct hostent *) NULL)
00241         {
00242             LOG (LOG_ERROR,"common::init_connection","Unknown host: %s",host);
00243             return -1;
00244         }
00245         memcpy(&insock.sin_addr, hostbn->h_addr, hostbn->h_length);
00246     }
00247     if (connect(fd,(struct sockaddr *)&insock,sizeof(insock)) == (-1))
00248     {
00249         LOG (LOG_ERROR,"common::init_connection","Can't connect to server");
00250             perror("Can't connect to server");
00251             return -1;
00252     }
00253 #else
00254     struct addrinfo hints;
00255     struct addrinfo *res = NULL, *ai;
00256     char port_str[6];
00257 
00258     /* See note in section above about null hosts names */
00259     if (!strcmp(host,"(null)")) return -1;
00260 
00261     snprintf(port_str, sizeof(port_str), "%d", port);
00262 
00263     memset(&hints, 0, sizeof(hints));
00264     hints.ai_family = AF_UNSPEC;
00265     hints.ai_socktype = SOCK_STREAM;
00266     hints.ai_protocol = IPPROTO_TCP;
00267 
00268     if (getaddrinfo(host, port_str, &hints, &res) != 0)
00269         return -1;
00270 
00271     for (ai = res; ai != NULL; ai = ai->ai_next) {
00272         fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
00273         if (fd == -1)
00274             continue;
00275 
00276         if (connect(fd, ai->ai_addr, ai->ai_addrlen) != 0) {
00277             close(fd);
00278             fd = -1;
00279             continue;
00280         }
00281 
00282         break;
00283     }
00284 
00285     freeaddrinfo(res);
00286     if (fd == -1)
00287         return -1;
00288 #endif
00289 
00290     free(csocket.servername);
00291     csocket.servername = malloc(sizeof(char)*(strlen(host)+1));
00292     strcpy(csocket.servername, host);
00293 
00294 #ifndef WIN32
00295     if (fcntl(fd, F_SETFL, O_NDELAY)==-1) {
00296         LOG (LOG_ERROR,"common::init_connection","Error on fcntl.");
00297     }
00298 #else
00299     {
00300                 unsigned long tmp = 1;
00301                 if (ioctlsocket(fd, FIONBIO, &tmp)<0) {
00302             LOG (LOG_ERROR,"common::init_connection","Error on ioctlsocket.");
00303         }
00304         }
00305 #endif
00306 
00307 #ifdef TCP_NODELAY
00308     /* turn off nagle algorithm */
00309     if (use_config[CONFIG_FASTTCP]) {
00310         int i=1;
00311 
00312 #ifdef WIN32
00313         if (setsockopt(fd, SOL_TCP, TCP_NODELAY, ( const char* )&i, sizeof(i)) == -1)
00314             perror("TCP_NODELAY");
00315 #else
00316         if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &i, sizeof(i)) == -1)
00317             perror("TCP_NODELAY");
00318 #endif
00319     }
00320 #endif
00321 
00322     if (getsockopt(fd,SOL_SOCKET,SO_RCVBUF, (char*)&oldbufsize, &buflen)==-1)
00323         oldbufsize=0;
00324 
00325     if (oldbufsize<newbufsize) {
00326         if(setsockopt(fd,SOL_SOCKET,SO_RCVBUF, (char*)&newbufsize, sizeof(&newbufsize))) {
00327             LOG(LOG_WARNING,"InitConnection: setsockopt"," unable to set output buf size to %d", newbufsize);
00328             setsockopt(fd,SOL_SOCKET,SO_RCVBUF, (char*)&oldbufsize, sizeof(&oldbufsize));
00329         }
00330     }
00331     return fd;
00332 }
00333 
00334 /* This function negotiates/establishes the connection with the
00335  * server.
00336  */
00337 
00338 void negotiate_connection(int sound)
00339 {
00340     int tries;
00341 
00342     SendVersion(csocket);
00343 
00344     /* We need to get the version command fairly early on because
00345      * we need to know if the server will support a request to use
00346      * png images.  This isn't done the best, because if the server
00347      * never sends the version command, we can loop here forever.
00348      * However, if it doesn't send the version command, we have no idea
00349      * what we are dealing with.
00350      */
00351     tries=0;
00352     while (csocket.cs_version==0) {
00353         DoClient(&csocket);
00354         if (csocket.fd == -1) return;
00355 
00356         usleep(10*1000);    /* 10 milliseconds */
00357         tries++;
00358         /* If we have't got a response in 10 seconds, bail out */
00359         if (tries > 1000) {
00360 #ifdef WIN32
00361             closesocket(csocket.fd);
00362 #else
00363             close(csocket.fd);
00364 #endif
00365             csocket.fd=-1;
00366             return;
00367         }
00368     }
00369 
00370     if (csocket.sc_version<1023) {
00371         LOG (LOG_WARNING,"common::negotiate_connection","Server does not support PNG images, yet that is all this client");
00372         LOG (LOG_WARNING,"common::negotiate_connection","supports.  Either the server needs to be upgraded, or you need to");
00373         LOG (LOG_WARNING,"common::negotiate_connection","downgrade your client.");
00374         exit(1);
00375     }
00376 
00377     /* If the user has specified a numeric face id, use it. If it is a string
00378      * like base, then that resolves to 0, so no real harm in that.
00379      */
00380     if (face_info.want_faceset) face_info.faceset = atoi(face_info.want_faceset);
00381     cs_print_string(csocket.fd,
00382             "setup map2cmd 1 tick 1 sound %d%s sexp %d darkness %d newmapcmd 1 spellmon 1 faceset %d facecache %d exp64 1 itemcmd 2",
00383             sound>=0, (sound>=0) ? " sound2 3" : "", want_skill_exp,
00384                     want_config[CONFIG_LIGHTING]?1:0, face_info.faceset,
00385                     want_config[CONFIG_CACHE]);
00386 
00387     /* We can do this right now also - isn't any reason to wait */
00388     cs_print_string(csocket.fd,"requestinfo exp_table");
00389 
00390     use_config[CONFIG_MAPHEIGHT]=want_config[CONFIG_MAPHEIGHT];
00391     use_config[CONFIG_MAPWIDTH]=want_config[CONFIG_MAPWIDTH];
00392     mapdata_set_size(use_config[CONFIG_MAPWIDTH], use_config[CONFIG_MAPHEIGHT]);
00393     if (use_config[CONFIG_MAPHEIGHT]!=11 || use_config[CONFIG_MAPWIDTH]!=11)
00394         cs_print_string(csocket.fd,"setup mapsize %dx%d",use_config[CONFIG_MAPWIDTH], use_config[CONFIG_MAPHEIGHT]);
00395 
00396     use_config[CONFIG_SMOOTH]=want_config[CONFIG_SMOOTH];
00397     if (use_config[CONFIG_SMOOTH]){ /*or other mapextended infos*/
00398         cs_print_string(csocket.fd,"setup extendedMapInfos 1");
00399         /*will handle all special infos requested when setup answer this command*/
00400     }
00401     cs_print_string(csocket.fd,"setup extendedTextInfos 1");
00402     cs_print_string(csocket.fd,"setup want_pickup 1");
00403     cs_print_string(csocket.fd,"setup inscribe 1");
00404 
00405     /* If the server will answer the requestinfo for image_info and image_data,
00406      * send it and wait for the response.
00407      */
00408     if (csocket.sc_version >= 1027) {
00409         /* last_start is -99.  This means the first face requested will
00410          * be 1 (not 0) - this is OK because 0 is defined as the blank
00411          * face.
00412          */
00413         int last_end=0, last_start=-99;
00414 
00415         cs_print_string(csocket.fd,"requestinfo image_info");
00416         requestinfo_sent = RI_IMAGE_INFO;
00417         replyinfo_status = 0;
00418         replyinfo_last_face = 0;
00419 
00420         do {
00421             DoClient(&csocket);
00422 
00423             /* it's rare, the connection can die while getting
00424              * this info.
00425              */
00426             if (csocket.fd == -1) return;
00427 
00428             if (use_config[CONFIG_DOWNLOAD]) {
00429                 /* we need to know how many faces to
00430                  * be able to make the request intelligently.
00431                  * So only do the following block if we have that info.
00432                  * By setting the sent flag, we will never exit
00433                  * this loop until that happens.
00434                  */
00435                 requestinfo_sent |= RI_IMAGE_SUMS;
00436                 if (face_info.num_images != 0) {
00437                     /* Sort of fake things out - if we have sent the
00438                      * request for image sums but have not got them all answered
00439                      * yet, we then clear the bit from the status
00440                      * so we continue to loop.
00441                      */
00442                     if (last_end == face_info.num_images) {
00443                         /* Mark that we're all done */
00444                         if (replyinfo_last_face == last_end) {
00445                             replyinfo_status |= RI_IMAGE_SUMS;
00446                             image_update_download_status(face_info.num_images, face_info.num_images, face_info.num_images);
00447                         }
00448                     } else {
00449                         /* If we are all caught up, request another
00450                          * 100 sums.
00451                          */
00452                         if (last_end == replyinfo_last_face) {
00453                             last_start += 100;
00454                             last_end += 100;
00455                             if (last_end > face_info.num_images) last_end = face_info.num_images;
00456                             cs_print_string(csocket.fd,"requestinfo image_sums %d %d", last_start, last_end);
00457                             image_update_download_status(last_start, last_end, face_info.num_images);
00458                         }
00459                     }
00460                 } /* Still have image_sums request to send */
00461             } /* endif download all faces */
00462 
00463             usleep(10*1000);    /* 10 milliseconds */
00464             /* Don't put in an upper time limit with tries like we did above - if the
00465              * player is downloading all the images, the time this takes could be
00466              * considerable.
00467              */
00468 
00469         } while (replyinfo_status != requestinfo_sent);
00470     }
00471     if (use_config[CONFIG_DOWNLOAD]) {
00472         char buf[MAX_BUF];
00473 
00474         snprintf(buf, sizeof(buf), "Download of images complete.  Found %d locally, downloaded %d from server\n",
00475                 face_info.cache_hits, face_info.cache_misses);
00476         draw_info(buf, NDI_GOLD);
00477     }
00478 
00479     /* This needs to get changed around - we really don't want to send
00480      * the SendAddMe until we do all of our negotiation, which may include
00481      * things like downloading all the images and whatnot - this is more an
00482      * issue if the user is not using the default face set, as in that case,
00483      * we might end up building images from the wrong set.
00484      */
00485     SendAddMe(csocket);
00486 }