1 |
/* |
2 |
CrossFire, A Multiplayer game for X-windows |
3 |
|
4 |
Copyright (C) 2002-2003 Mark Wedel & The Crossfire Development Team |
5 |
Copyright (C) 1992 Frank Tore Johansen |
6 |
|
7 |
This program is free software; you can redistribute it and/or modify |
8 |
it under the terms of the GNU General Public License as published by |
9 |
the Free Software Foundation; either version 2 of the License, or |
10 |
(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., 675 Mass Ave, Cambridge, MA 02139, USA. |
20 |
|
21 |
The author can be reached via e-mail to <crossfire@schmorp.de> |
22 |
*/ |
23 |
|
24 |
/** |
25 |
* \file |
26 |
* Main client/server loops. |
27 |
* |
28 |
* \date 2003-12-02 |
29 |
* |
30 |
* loop.c mainly deals with initialization and higher level socket |
31 |
* maintenance (checking for lost connections and if data has arrived.) |
32 |
* The reading of data is handled in ericserver.c |
33 |
*/ |
34 |
|
35 |
|
36 |
#include <global.h> |
37 |
#include <sproto.h> |
38 |
#include <sockproto.h> |
39 |
|
40 |
#include <sys/types.h> |
41 |
#include <sys/time.h> |
42 |
#include <sys/socket.h> |
43 |
#include <netinet/in.h> |
44 |
#include <netdb.h> |
45 |
|
46 |
#include <unistd.h> |
47 |
#include <arpa/inet.h> |
48 |
|
49 |
#include <loader.h> |
50 |
#include <newserver.h> |
51 |
|
52 |
/***************************************************************************** |
53 |
* Start of command dispatch area. |
54 |
* The commands here are protocol commands. |
55 |
****************************************************************************/ |
56 |
|
57 |
/* Either keep this near the start or end of the file so it is |
58 |
* at least reasonablye easy to find. |
59 |
* There are really 2 commands - those which are sent/received |
60 |
* before player joins, and those happen after the player has joined. |
61 |
* As such, we have function types that might be called, so |
62 |
* we end up having 2 tables. |
63 |
*/ |
64 |
|
65 |
enum { |
66 |
PF_PLAYER = 0x01, // must have valid player |
67 |
PF_IMMEDIATE = 0x02, // TODO: hack, can be executed immediately |
68 |
PF_PLAYING = 0x04, // must be in playing state |
69 |
}; |
70 |
|
71 |
struct pkt_type |
72 |
{ |
73 |
const char *name; |
74 |
void *cb; |
75 |
int flags; |
76 |
|
77 |
bool may_execute (client_socket *ns) |
78 |
{ |
79 |
return (!(flags & PF_PLAYER) || ns->pl) |
80 |
&& (!(flags & PF_PLAYING) || (ns->pl && ns->pl->state == ST_PLAYING)); |
81 |
} |
82 |
|
83 |
void execute (client_socket *ns, char *data, int datalen) |
84 |
{ |
85 |
//TODO: only one format |
86 |
if (flags & PF_PLAYER) |
87 |
((void (*)(char *, int, player * ))cb)((char *)data, datalen, ns->pl); |
88 |
else |
89 |
((void (*)(char *, int, client_socket *))cb)((char *)data, datalen, ns); |
90 |
} |
91 |
}; |
92 |
|
93 |
// SocketCommand, PlayingCommand, should not exist with those ugly casts |
94 |
#define SC(cb) (void *)static_cast<void (*)(char *, int, client_socket *)>(cb), |
95 |
#define PC(cb) (void *)static_cast<void (*)(char *, int, player *)>(cb), PF_PLAYER | |
96 |
|
97 |
/** |
98 |
* Dispatch table for the server. |
99 |
*/ |
100 |
static struct pkt_type packets[] = { |
101 |
{"ncom", PC(NewPlayerCmd) PF_PLAYING }, |
102 |
{"command", PC(PlayerCmd) PF_PLAYING }, |
103 |
|
104 |
{"examine", PC(ExamineCmd) PF_PLAYING | PF_IMMEDIATE }, |
105 |
{"apply", PC(ApplyCmd) PF_PLAYING | PF_IMMEDIATE }, |
106 |
{"reply", PC(ReplyCmd) PF_IMMEDIATE }, |
107 |
{"lookat", PC(LookAt) PF_PLAYING | PF_IMMEDIATE }, |
108 |
{"lock", PC(LockItem) PF_PLAYING | PF_IMMEDIATE }, |
109 |
{"mark", PC(MarkItem) PF_PLAYING | PF_IMMEDIATE }, |
110 |
{"move", PC(MoveCmd) PF_PLAYING | PF_IMMEDIATE }, |
111 |
{"ext", PC(ExtCmd) PF_IMMEDIATE }, /* CF+ */ |
112 |
{"mapredraw", PC(MapRedrawCmd) PF_IMMEDIATE }, /* Added: phil */ |
113 |
{"mapinfo", PC(MapInfoCmd) PF_IMMEDIATE }, /* CF+ */ |
114 |
|
115 |
{"addme", SC(AddMeCmd) PF_IMMEDIATE }, |
116 |
{"askface", SC(SendFaceCmd) 0 }, /* Added: phil */ |
117 |
{"requestinfo", SC(RequestInfo) 0 }, |
118 |
{"setfacemode", SC(SetFaceMode) PF_IMMEDIATE }, |
119 |
{"setsound", SC(SetSound) PF_IMMEDIATE }, |
120 |
{"setup", SC(SetUp) PF_IMMEDIATE }, |
121 |
{"version", SC(VersionCmd) PF_IMMEDIATE }, |
122 |
{"toggleextendedinfos", SC(ToggleExtendedInfos) PF_IMMEDIATE }, /*Added: tchize */ |
123 |
{"toggleextendedtext", SC(ToggleExtendedText) PF_IMMEDIATE }, /*Added: tchize */ |
124 |
{"asksmooth", SC(AskSmooth) 0 }, /*Added: tchize (smoothing technologies) */ |
125 |
}; |
126 |
|
127 |
/** |
128 |
* RequestInfo is sort of a meta command. There is some specific |
129 |
* request of information, but we call other functions to provide |
130 |
* that information. |
131 |
*/ |
132 |
void |
133 |
RequestInfo (char *buf, int len, client_socket * ns) |
134 |
{ |
135 |
char *params = NULL, *cp; |
136 |
|
137 |
/* No match */ |
138 |
char bigbuf[MAX_BUF]; |
139 |
int slen; |
140 |
|
141 |
/* Set up replyinfo before we modify any of the buffers - this is used |
142 |
* if we don't find a match. |
143 |
*/ |
144 |
strcpy (bigbuf, "replyinfo "); |
145 |
slen = strlen (bigbuf); |
146 |
safe_strcat (bigbuf, buf, &slen, MAX_BUF); |
147 |
|
148 |
/* find the first space, make it null, and update the |
149 |
* params pointer. |
150 |
*/ |
151 |
for (cp = buf; *cp != '\0'; cp++) |
152 |
if (*cp == ' ') |
153 |
{ |
154 |
*cp = '\0'; |
155 |
params = cp + 1; |
156 |
break; |
157 |
} |
158 |
|
159 |
if (!strcmp (buf, "image_info")) |
160 |
send_image_info (ns, params); |
161 |
else if (!strcmp (buf, "image_sums")) |
162 |
send_image_sums (ns, params); |
163 |
else if (!strcmp (buf, "skill_info")) |
164 |
send_skill_info (ns, params); |
165 |
else if (!strcmp (buf, "spell_paths")) |
166 |
send_spell_paths (ns, params); |
167 |
else |
168 |
ns->send_packet (bigbuf, len); |
169 |
} |
170 |
|
171 |
/** |
172 |
* Handle client input. |
173 |
* |
174 |
* HandleClient is actually not named really well - we only get here once |
175 |
* there is input, so we don't do exception or other stuff here. |
176 |
* sock is the output socket information. pl is the player associated |
177 |
* with this socket, null if no player (one of the init_sockets for just |
178 |
* starting a connection) |
179 |
*/ |
180 |
|
181 |
void |
182 |
HandleClient (client_socket *ns, player *pl) |
183 |
{ |
184 |
/* Loop through this - maybe we have several complete packets here. */ |
185 |
// limit to a few commands only, though, as to not monopolise the server |
186 |
for (int repeat = 16; repeat--;) |
187 |
{ |
188 |
/* If it is a player, and they don't have any speed left, we |
189 |
* return, and will read in the data when they do have time. |
190 |
*/ |
191 |
if (pl && pl->state == ST_PLAYING && pl->ob && pl->ob->speed_left < 0) |
192 |
return; |
193 |
|
194 |
int pkt_len = ns->read_packet (); |
195 |
|
196 |
if (pkt_len < 0) |
197 |
{ |
198 |
LOG (llevError, "read error on player %s\n", &pl->ob->name); |
199 |
/* Caller will take care of cleaning this up */ |
200 |
ns->status = Ns_Dead; |
201 |
return; |
202 |
} |
203 |
else if (pkt_len == 0) |
204 |
/* Still dont have a full packet */ |
205 |
return; |
206 |
|
207 |
/* First, break out beginning word. There are at least |
208 |
* a few commands that do not have any paremeters. If |
209 |
* we get such a command, don't worry about trying |
210 |
* to break it up. |
211 |
*/ |
212 |
int datalen; |
213 |
char *data = strchr ((char *)ns->inbuf + 2, ' '); |
214 |
|
215 |
if (data) |
216 |
{ |
217 |
*data++ = 0; |
218 |
datalen = pkt_len - (data - (char *)ns->inbuf); |
219 |
} |
220 |
else |
221 |
{ |
222 |
data = (char *)ns->inbuf + 2; // better read garbage than segfault |
223 |
datalen = 0; |
224 |
} |
225 |
|
226 |
ns->inbuf [pkt_len] = 0; /* Terminate buffer - useful for string data */ |
227 |
|
228 |
for (pkt_type *pkt = packets; pkt < packets + (sizeof (packets) / sizeof (packets[0])); ++pkt) |
229 |
if (!strcmp ((char *)ns->inbuf + 2, pkt->name)) |
230 |
{ |
231 |
if (pkt->may_execute (ns)) |
232 |
{ |
233 |
pkt->execute (ns, data, datalen); |
234 |
ns->skip_packet (pkt_len); |
235 |
//D//TODO not doing this causes random memory corruption |
236 |
if (pkt->flags & PF_IMMEDIATE) |
237 |
goto next_packet; |
238 |
return; |
239 |
} |
240 |
} |
241 |
|
242 |
/* If we get here, we didn't find a valid command. Logging |
243 |
* this might be questionable, because a broken client/malicious |
244 |
* user could certainly send a whole bunch of invalid commands. |
245 |
*/ |
246 |
LOG (llevDebug, "Bad command from client (%s)\n", ns->inbuf + 2); |
247 |
ns->skip_packet (pkt_len); |
248 |
next_packet: |
249 |
; |
250 |
} |
251 |
} |
252 |
|
253 |
void |
254 |
flush_sockets (void) |
255 |
{ |
256 |
for (sockvec::iterator i = client_sockets.begin (); i != client_sockets.end (); ++i) |
257 |
if ((*i)->status != Ns_Dead) |
258 |
(*i)->flush (); |
259 |
} |
260 |
|
261 |
/** |
262 |
* This checks the sockets for input, does the right thing. |
263 |
* |
264 |
* A bit of this code is grabbed out of socket.c |
265 |
* There are 2 lists we need to look through - init_sockets is a list |
266 |
* |
267 |
*/ |
268 |
void |
269 |
doeric_server (void) |
270 |
{ |
271 |
int i, pollret; |
272 |
fd_set tmp_read; |
273 |
struct sockaddr_in addr; |
274 |
socklen_t addrlen = sizeof (struct sockaddr); |
275 |
player *pl, *next; |
276 |
int maxfd = 0; |
277 |
|
278 |
#ifdef CS_LOGSTATS |
279 |
if ((time (NULL) - cst_lst.time_start) >= CS_LOGTIME) |
280 |
write_cs_stats (); |
281 |
#endif |
282 |
|
283 |
/* Go through the players. Let the loop set the next pl value, |
284 |
* since we may remove some |
285 |
*/ |
286 |
//TODO: must be handled cleanly elsewhere |
287 |
for (pl = first_player; pl; ) |
288 |
{ |
289 |
player *npl = pl->next; |
290 |
|
291 |
//TODO: must be handled cleanly elsewhere |
292 |
if (pl->socket->status == Ns_Dead) |
293 |
{ |
294 |
save_player (pl->ob, 0); |
295 |
|
296 |
if (!QUERY_FLAG (pl->ob, FLAG_REMOVED)) |
297 |
{ |
298 |
terminate_all_pets (pl->ob); |
299 |
pl->ob->remove (); |
300 |
} |
301 |
|
302 |
leave (pl, 1); |
303 |
final_free_player (pl); |
304 |
} |
305 |
|
306 |
pl = npl; |
307 |
} |
308 |
|
309 |
FD_ZERO (&tmp_read); |
310 |
|
311 |
for (sockvec::iterator i = client_sockets.begin (); i != client_sockets.end (); ) |
312 |
{ |
313 |
client_socket *s = *i; |
314 |
|
315 |
if (s->status == Ns_Dead) |
316 |
{ |
317 |
client_sockets.erase (i); |
318 |
delete s; |
319 |
} |
320 |
else |
321 |
{ |
322 |
if (s->fd > maxfd) maxfd = s->fd; |
323 |
|
324 |
FD_SET (s->fd, &tmp_read); |
325 |
|
326 |
++i; |
327 |
} |
328 |
} |
329 |
|
330 |
struct timeval timeout; |
331 |
|
332 |
timeout.tv_sec = 0; |
333 |
timeout.tv_usec = 0; |
334 |
|
335 |
pollret = select (maxfd + 1, |
336 |
&tmp_read, 0, 0, |
337 |
&timeout); |
338 |
|
339 |
if (pollret == -1) |
340 |
{ |
341 |
LOG (llevError, "select failed: %s\n", strerror (errno)); |
342 |
return; |
343 |
} |
344 |
|
345 |
/* We need to do some of the processing below regardless */ |
346 |
|
347 |
/* Check for any input on the sockets */ |
348 |
for (sockvec::iterator i = client_sockets.begin (); i != client_sockets.end (); ++i) |
349 |
{ |
350 |
client_socket *s = *i; |
351 |
player *pl = s->pl; |
352 |
|
353 |
//TODO: disassociate handleclient from socket readin |
354 |
if (s->inbuf_len || FD_ISSET (s->fd, &tmp_read)) |
355 |
HandleClient (s, pl); |
356 |
|
357 |
//TODO: should not be done here, either |
358 |
if (s->status != Ns_Dead && pl) |
359 |
{ |
360 |
/* Update the players stats once per tick. More efficient than |
361 |
* sending them whenever they change, and probably just as useful |
362 |
*/ |
363 |
esrv_update_stats (pl); |
364 |
if (pl->last_weight != -1 && pl->last_weight != WEIGHT (pl->ob)) |
365 |
{ |
366 |
esrv_update_item (UPD_WEIGHT, pl->ob, pl->ob); |
367 |
if (pl->last_weight != WEIGHT (pl->ob)) |
368 |
LOG (llevError, "esrv_update_item(UPD_WEIGHT) did not set player weight: is %lu, should be %lu\n", |
369 |
(unsigned long) pl->last_weight, WEIGHT (pl->ob)); |
370 |
} |
371 |
|
372 |
/* draw_client_map does sanity checking that map is |
373 |
* valid, so don't do it here. |
374 |
*/ |
375 |
draw_client_map (pl->ob); |
376 |
if (s->update_look) |
377 |
esrv_draw_look (pl->ob); |
378 |
} |
379 |
} |
380 |
} |
381 |
|