Crossfire Client, Trunk  R20693
client.c
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
32 #include "client.h"
33 
34 #include <ctype.h>
35 #include <errno.h>
36 #include <gio/gio.h>
37 #ifdef HAVE_GIO_GNETWORKING_H
38 #include <gio/gnetworking.h>
39 #endif
40 
41 #include "external.h"
42 #include "mapdata.h"
43 #include "metaserver.h"
44 #include "script.h"
45 
46 /* actually declare the globals */
47 
49 
51 char *sound_server = BINDIR "/cfsndserv";
52 const char *config_dir;
53 const char *cache_dir;
54 
56 
60 
61 guint32 tick=0;
62 
63 guint16 exp_table_max=0;
64 guint64 *exp_table=NULL;
65 
67 
70 static GInputStream *in;
71 
72 const char *const resists_name[NUM_RESISTS] = {
73  "armor", "magic", "fire", "elec",
74  "cold", "conf", "acid", "drain",
75  "ghit", "pois", "slow", "para",
76  "t undead", "fear", "depl","death",
77  "hword", "blind"
78 };
79 
80 typedef void (*CmdProc)(unsigned char *, int len);
81 
87 struct CmdMapping {
88  const char *cmdname;
89  void (*cmdproc)(unsigned char *, int );
91 };
92 
100 struct CmdMapping commands[] = {
101  /*
102  * The order of this table does not make much of a difference. Related
103  * commands are listed in groups.
104  */
105  { "map2", Map2Cmd, SHORT_ARRAY },
106  { "map_scroll", (CmdProc)map_scrollCmd, ASCII },
107  { "magicmap", MagicMapCmd, MIXED /* ASCII, then binary */},
108  { "newmap", NewmapCmd, NODATA },
109  { "mapextended", MapExtendedCmd, MIXED /* chars, then SHORT_ARRAY */ },
110 
111  { "item2", Item2Cmd, MIXED },
112  { "upditem", UpdateItemCmd, MIXED },
113  { "delitem", DeleteItem, INT_ARRAY },
114  { "delinv", DeleteInventory, ASCII },
115 
116  { "addspell", AddspellCmd, MIXED },
117  { "updspell", UpdspellCmd, MIXED },
118  { "delspell", DeleteSpell, INT_ARRAY },
119 
120  { "drawinfo", (CmdProc)DrawInfoCmd, ASCII },
121  { "drawextinfo", (CmdProc)DrawExtInfoCmd, ASCII},
122  {
123  "stats", StatsCmd, STATS /* Array of: int8, (int?s for
124  * that stat)
125  */
126  },
127  { "image2", Image2Cmd, MIXED /* int, int8, int, PNG */ },
128  {
129  "face2", Face2Cmd, MIXED /* int16, int8, int32, string
130  */
131  },
132  { "tick", TickCmd, INT_ARRAY /* uint32 */},
133 
134  { "music", (CmdProc)MusicCmd, ASCII },
135  {
136  "sound2", Sound2Cmd, MIXED /* int8, int8, int8, int8,
137  * int8, int8, chars, int8,
138  * chars
139  */
140  },
141  { "anim", AnimCmd, SHORT_ARRAY},
142  { "smooth", SmoothCmd, SHORT_ARRAY},
143 
144  { "player", PlayerCmd, MIXED /* 3 ints, int8, str */ },
145  { "comc", CompleteCmd, SHORT_INT },
146 
147  { "addme_failed", (CmdProc)AddMeFail, NODATA },
148  { "addme_success", (CmdProc)AddMeSuccess, NODATA },
149  { "version", (CmdProc)VersionCmd, ASCII },
150  { "goodbye", (CmdProc)GoodbyeCmd, NODATA },
151  { "setup", (CmdProc)SetupCmd, ASCII},
152  { "failure", (CmdProc)FailureCmd, ASCII},
153  { "accountplayers", (CmdProc)AccountPlayersCmd, ASCII},
154 
155  { "query", (CmdProc)handle_query, ASCII},
156  { "replyinfo", ReplyInfoCmd, ASCII},
157  { "ExtendedTextSet", (CmdProc)SinkCmd, NODATA},
158  { "ExtendedInfoSet", (CmdProc)SinkCmd, NODATA},
159 
160  { "pickup", PickupCmd, INT_ARRAY /* uint32 */},
161 };
162 
166 #define NCOMMANDS ((int)(sizeof(commands)/sizeof(struct CmdMapping)))
167 
168 GQuark client_error_quark();
169 
170 void client_mapsize(int width, int height) {
171  // Store desired size in use_config to check results from the server.
174 
175  // Set map size in case we receive 'map' before 'setup' commands.
176  mapdata_set_size(width, height);
177  cs_print_string(csocket.fd, "setup mapsize %dx%d", width, height);
178 }
179 
181  LOG(LOG_DEBUG, "close_server_connection", "Closing server connection");
182  g_io_stream_close(G_IO_STREAM(csocket.fd), NULL, NULL);
183  g_object_unref(csocket.fd);
184  csocket.fd = NULL;
185 }
186 
187 void client_run() {
188  GError* err = NULL;
189  SockList inbuf;
190  inbuf.buf = g_malloc(MAXSOCKBUF);
191  if (!SockList_ReadPacket(csocket.fd, &inbuf, MAXSOCKBUF - 1, &err)) {
192  /*
193  * If a socket error occurred while reading the packet, drop the
194  * server connection. Is there a better way to handle this?
195  */
196  LOG(LOG_ERROR, "client_run", "%s", err->message);
197  g_error_free(err);
199  return;
200  }
201  if (inbuf.len == 0) {
203  return;
204  }
205  /*
206  * Null-terminate the buffer, and set the data pointer so it points
207  * to the first character of the data (following the packet length).
208  */
209  inbuf.buf[inbuf.len] = '\0';
210  unsigned char* data = inbuf.buf + 2;
211  /*
212  * Commands that provide data are always followed by a space. Find
213  * the space and convert it to a null character. If no spaces are
214  * found, the packet contains a command with no associatd data.
215  */
216  while ((*data != ' ') && (*data != '\0')) {
217  ++data;
218  }
219  int len;
220  if (*data == ' ') {
221  *data = '\0';
222  data++;
223  len = inbuf.len - (data - inbuf.buf);
224  } else {
225  len = 0;
226  }
227  /*
228  * Search for the command in the list of supported server commands.
229  * If the server command is supported by the client, let the script
230  * watcher know what command was received, then process it and quit
231  * searching the command list.
232  */
233  int i;
234  for(i = 0; i < NCOMMANDS; i++) {
235  const char *cmdin = (char *)inbuf.buf + 2;
236  if (strcmp(cmdin, commands[i].cmdname) == 0) {
237  script_watch(cmdin, data, len, commands[i].cmdformat);
238  commands[i].cmdproc(data, len);
239  break;
240  }
241  }
242  /*
243  * After processing the command, mark the socket input buffer empty.
244  */
245  inbuf.len=0;
246  /*
247  * Complain about unsupported commands to facilitate troubleshooting.
248  * The client and server should negotiate a connection such that the
249  * server does not send commands the client does not support.
250  */
251  if (i == NCOMMANDS) {
252  LOG(LOG_ERROR, "client_run", "Unrecognized command from server (%s)\n",
253  inbuf.buf+2);
254  }
255  g_free(inbuf.buf);
256 }
257 
258 void client_connect(const char hostname[static 1]) {
259  GSocketClient *sclient = g_socket_client_new();
260 
261  // Store server hostname.
262  if (csocket.servername != NULL) {
263  g_free(csocket.servername);
264  }
265  csocket.servername = g_strdup(hostname);
266 
267  // Try to connect to server.
268  csocket.fd = g_socket_client_connect_to_host(
269  sclient, hostname, use_config[CONFIG_PORT], NULL, NULL);
270  g_object_unref(sclient);
271  if (csocket.fd == NULL) {
272  return;
273  }
274 
275  GSocket *socket = g_socket_connection_get_socket(csocket.fd);
276  int i = 1, fd = g_socket_get_fd(socket);
277 #ifndef WIN32
278 #ifdef HAVE_GIO_GNETWORKING_H
279  if (use_config[CONFIG_FASTTCP]) {
280  if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &i, sizeof(i)) == -1) {
281  perror("TCP_NODELAY");
282  }
283  }
284 #endif
285 #endif
286  in = g_io_stream_get_input_stream(G_IO_STREAM(csocket.fd));
287 }
288 
290  return csocket.fd != NULL && g_socket_connection_is_connected(csocket.fd);
291 }
292 
293 GSource *client_get_source() {
294  return g_pollable_input_stream_create_source(
295  G_POLLABLE_INPUT_STREAM(in), NULL);
296 }
297 
298 void client_negotiate(int sound) {
299  int tries;
300 
301  SendVersion(csocket);
302 
303  /* We need to get the version command fairly early on because we need to
304  * know if the server will support a request to use png images. This
305  * isn't done the best, because if the server never sends the version
306  * command, we can loop here forever. However, if it doesn't send the
307  * version command, we have no idea what we are dealing with.
308  */
309  tries=0;
310  while (csocket.cs_version==0) {
311  client_run();
312  if (csocket.fd == NULL) {
313  return;
314  }
315 
316  usleep(10*1000); /* 10 milliseconds */
317  tries++;
318  /* If we haven't got a response in 10 seconds, bail out */
319  if (tries > 1000) {
320  LOG (LOG_ERROR,"common::negotiate_connection", "Connection timed out");
322  return;
323  }
324  }
325 
326  if (csocket.sc_version<1023) {
327  LOG (LOG_WARNING,"common::negotiate_connection","Server does not support PNG images, yet that is all this client");
328  LOG (LOG_WARNING,"common::negotiate_connection","supports. Either the server needs to be upgraded, or you need to");
329  LOG (LOG_WARNING,"common::negotiate_connection","downgrade your client.");
330  exit(1);
331  }
332 
333  /* If the user has specified a numeric face id, use it. If it is a string
334  * like base, then that resolves to 0, so no real harm in that.
335  */
336  if (face_info.want_faceset) {
338  }
339 
340  /* For sound, a value following determines which sound features are
341  * wanted. The value is 1 for sound effects, and 2 for background music,
342  * or the sum of 1 + 2 (3) for both.
343  *
344  * For spellmon, try each acceptable level, but make sure the one the
345  * client prefers is last.
346  */
347  cs_print_string(csocket.fd, "setup "
348  "map2cmd 1 tick 1 sound2 %d darkness %d spellmon 1 spellmon 2 "
349  "faceset %d facecache %d want_pickup 1 loginmethod %d newmapcmd 1",
350  (sound >= 0) ? 3 : 0, want_config[CONFIG_LIGHTING] ? 1 : 0,
352 
353  /*
354  * We can do this right now also. There is not any reason to wait.
355  */
356  cs_print_string(csocket.fd, "requestinfo skill_info");
357  cs_print_string(csocket.fd,"requestinfo exp_table");
358  /*
359  * While these are only used for new login method, they should become
360  * standard fairly soon. All of these are pretty small, and do not add
361  * much to the cost. They make it more likely that the information is
362  * ready when the window that needs it is raised.
363  */
364  cs_print_string(csocket.fd,"requestinfo motd");
365  cs_print_string(csocket.fd,"requestinfo news");
366  cs_print_string(csocket.fd,"requestinfo rules");
367 
370 
371  /* If the server will answer the requestinfo for image_info and image_data,
372  * send it and wait for the response.
373  */
374  if (csocket.sc_version >= 1027) {
375  /* last_start is -99. This means the first face requested will be 1
376  * (not 0) - this is OK because 0 is defined as the blank face.
377  */
378  int last_end=0, last_start=-99;
379 
380  cs_print_string(csocket.fd,"requestinfo image_info");
382  replyinfo_status = 0;
384 
385  do {
386  client_run();
387  /*
388  * It is rare, but the connection can die while getting this info.
389  */
390  if (csocket.fd == NULL) {
391  return;
392  }
393 
395  /*
396  * We need to know how many faces to be able to make the
397  * request intelligently. So only do the following block if
398  * we have that info. By setting the sent flag, we will never
399  * exit this loop until that happens.
400  */
402  if (face_info.num_images != 0) {
403  /*
404  * Sort of fake things out - if we have sent the request
405  * for image sums but have not got them all answered yet,
406  * we then clear the bit from the status so we continue to
407  * loop.
408  */
409  if (last_end == face_info.num_images) {
410  /* Mark that we're all done */
411  if (replyinfo_last_face == last_end) {
414  }
415  } else {
416  /*
417  * If we are all caught up, request another 100 sums.
418  */
419  if (last_end <= (replyinfo_last_face+100)) {
420  last_start += 100;
421  last_end += 100;
422  if (last_end > face_info.num_images) {
423  last_end = face_info.num_images;
424  }
425  cs_print_string(csocket.fd,"requestinfo image_sums %d %d", last_start, last_end);
426  image_update_download_status(last_start, last_end, face_info.num_images);
427  }
428  }
429  } /* Still have image_sums request to send */
430  } /* endif download all faces */
431 
432  usleep(10*1000); /* 10 milliseconds */
433  /*
434  * Do not put in an upper time limit with tries like we did above.
435  * If the player is downloading all the images, the time this
436  * takes could be considerable.
437  */
438  } while (replyinfo_status != requestinfo_sent);
439  }
441  char buf[MAX_BUF];
442 
443  snprintf(buf, sizeof(buf), "Download of images complete. Found %d locally, downloaded %d from server\n",
446  }
447 
448  /* This needs to get changed around - we really don't want to send the
449  * SendAddMe until we do all of our negotiation, which may include things
450  * like downloading all the images and whatnot - this is more an issue if
451  * the user is not using the default face set, as in that case, we might
452  * end up building images from the wrong set.
453  * Only run this if not using new login method
454  */
455  if (!serverloginmethod) {
456  SendAddMe(csocket);
457  }
458 }
void PickupCmd(guint8 *data, int len)
Definition: commands.c:2377
void DeleteItem(unsigned char *data, int len)
Definition: commands.c:1797
int maxfd
Definition: client.c:58
void handle_query(char *data, int len)
Definition: commands.c:1529
#define CONFIG_FASTTCP
Definition: client.h:187
void client_negotiate(int sound)
Definition: client.c:298
guint32 width
Definition: image.c:42
size_t len
Definition: newclient.h:685
void SetupCmd(char *buf, int len)
Definition: commands.c:901
GSocketConnection * fd
Definition: client.h:120
guint32 tick
Definition: client.c:61
gint16 use_config[CONFIG_NUMS]
Definition: init.c:40
unsigned char buf[MAXSOCKBUF]
Definition: newclient.h:686
void(* CmdProc)(unsigned char *, int len)
Definition: client.c:80
void DrawInfoCmd(char *data, int len)
Definition: commands.c:1200
int replyinfo_last_face
Definition: client.c:58
void TickCmd(guint8 *data, int len)
Definition: commands.c:2360
void Sound2Cmd(unsigned char *data, int len)
Definition: sound.c:40
const char * cmdname
Definition: client.c:88
Definition: script.h:49
bool SockList_ReadPacket(GSocketConnection c[static 1], SockList sl[static 1], size_t len, GError **error)
Definition: newsocket.c:206
int sc_version
Definition: client.h:121
#define CONFIG_LIGHTING
Definition: client.h:201
#define MAX_SKILL
Definition: client.h:84
void AddMeFail(char *data, int len)
Definition: commands.c:1083
Warning that something might not work.
Definition: client.h:443
#define RI_IMAGE_SUMS
Definition: client.h:528
void ReplyInfoCmd(unsigned char *buf, int len)
Definition: commands.c:774
ClientSocket csocket
Definition: client.c:69
int wantloginmethod
Definition: client.c:59
Face_Information face_info
Definition: image.c:169
#define SOL_TCP
Definition: client.h:37
static GInputStream * in
Definition: client.c:70
#define MSG_TYPE_CLIENT
Definition: newclient.h:395
void MapExtendedCmd(unsigned char *data, int len)
Definition: commands.c:2234
int requestinfo_sent
Definition: client.c:57
const char * cache_dir
Definition: client.c:53
const char *const resists_name[NUM_RESISTS]
Definition: client.c:72
void MusicCmd(const char *data, int len)
Definition: sound.c:122
NameMapping resist_mapping[NUM_RESISTS]
Definition: client.c:66
int serverloginmethod
Definition: client.c:59
void DeleteInventory(unsigned char *data, int len)
Definition: commands.c:1823
char VERSION_INFO[MAX_BUF]
Definition: client.c:48
void script_watch(const char *cmd, const guint8 *data_initial, const int data_len, const enum CmdFormat format)
Definition: script.c:624
char * skill_names[MAX_SKILL]
Definition: client.c:50
void FailureCmd(char *buf, int len)
Definition: commands.c:2391
void Item2Cmd(unsigned char *data, int len)
Definition: commands.c:1702
guint64 * exp_table
Definition: client.c:64
void map_scrollCmd(char *data, int len)
Definition: commands.c:2166
void AccountPlayersCmd(char *buf, int len)
Definition: commands.c:2433
void LOG(LogLevel level, const char *origin, const char *format,...)
Definition: misc.c:109
void client_disconnect()
Definition: client.c:180
#define RI_IMAGE_INFO
Definition: client.h:527
void Map2Cmd(unsigned char *data, int len)
Definition: commands.c:2053
void UpdspellCmd(unsigned char *data, int len)
Definition: commands.c:1935
#define NUM_RESISTS
Definition: client.h:451
void SinkCmd(unsigned char *data, int len)
Definition: commands.c:2349
char * servername
Definition: client.h:131
void NewmapCmd(unsigned char *data, int len)
Definition: commands.c:2027
void SmoothCmd(unsigned char *data, int len)
Definition: commands.c:1180
NameMapping skill_mapping[MAX_SKILL]
Definition: client.c:66
void UpdateItemCmd(unsigned char *data, int len)
Definition: commands.c:1713
CmdFormat
Definition: script.h:43
Client_Player cpl
Definition: client.c:68
GQuark client_error_quark()
Definition: client.h:640
guint32 height
Definition: image.c:42
GSource * client_get_source()
Definition: client.c:293
#define CONFIG_MAPHEIGHT
Definition: client.h:204
void StatsCmd(unsigned char *data, int len)
Definition: commands.c:1341
#define CONFIG_PORT
Definition: client.h:207
#define MAX_BUF
Definition: client.h:40
void AddspellCmd(unsigned char *data, int len)
Definition: commands.c:1851
#define CONFIG_CACHE
Definition: client.h:189
const char * config_dir
Definition: client.c:52
gint16 want_config[CONFIG_NUMS]
Definition: init.c:40
#define CONFIG_DOWNLOAD
Definition: client.h:185
void client_run()
Definition: client.c:187
void Face2Cmd(guint8 *data, int len)
Definition: image.c:526
int last_used_skills[MAX_SKILL+1]
Definition: client.c:55
void GoodbyeCmd(char *data, int len)
Definition: commands.c:1115
#define NCOMMANDS
Definition: client.c:166
void SendAddMe(ClientSocket csock)
Definition: init.c:75
void VersionCmd(char *data, int len)
Definition: init.c:44
int replyinfo_status
Definition: client.c:57
void Image2Cmd(guint8 *data, int len)
Definition: image.c:552
void client_connect(const char hostname[static 1])
Definition: client.c:258
Warning that something definitely didn&#39;t work.
Definition: client.h:444
Definition: script.h:48
void(* cmdproc)(unsigned char *, int)
Definition: client.c:89
void AddMeSuccess(char *data, int len)
Definition: commands.c:1099
void AnimCmd(unsigned char *data, int len)
Definition: commands.c:1135
char * sound_server
Definition: client.c:51
Definition: script.h:44
void DrawExtInfoCmd(char *data, int len)
Definition: commands.c:1264
#define MAXSOCKBUF
Definition: newclient.h:25
void PlayerCmd(unsigned char *data, int len)
Definition: commands.c:1602
void draw_ext_info(int orig_color, int type, int subtype, const char *message)
Definition: info.c:932
#define CONFIG_SMOOTH
Definition: client.h:210
void DeleteSpell(unsigned char *data, int len)
Definition: commands.c:1973
int want_skill_exp
Definition: client.c:57
void CompleteCmd(unsigned char *data, int len)
Definition: player.c:212
int cs_print_string(GSocketConnection *fd, const char *str,...)
Definition: newsocket.c:245
Definition: script.h:50
void SendVersion(ClientSocket csock)
Definition: init.c:70
void MagicMapCmd(unsigned char *data, int len)
Definition: commands.c:2296
Useful debugging information.
Definition: client.h:441
bool client_is_connected()
Definition: client.c:289
#define NDI_GOLD
Definition: newclient.h:234
void mapdata_set_size(int viewx, int viewy)
Definition: mapdata.c:614
guint16 exp_table_max
Definition: client.c:63
void image_update_download_status(int start, int end, int total)
Definition: image.c:461
enum CmdFormat cmdformat
Definition: client.c:90
#define CONFIG_MAPWIDTH
Definition: client.h:203
void client_mapsize(int width, int height)
Definition: client.c:170
int cs_version
Definition: client.h:121
struct CmdMapping commands[]
Definition: client.c:100
#define MSG_TYPE_CLIENT_CONFIG
Definition: newclient.h:659