Crossfire Client, Branch
R11627
|
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 }