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 |
* Handle client input. |
129 |
* |
130 |
* HandleClient is actually not named really well - we only get here once |
131 |
* there is input, so we don't do exception or other stuff here. |
132 |
* sock is the output socket information. pl is the player associated |
133 |
* with this socket, null if no player (one of the init_sockets for just |
134 |
* starting a connection) |
135 |
*/ |
136 |
|
137 |
void |
138 |
HandleClient (client_socket *ns, player *pl) |
139 |
{ |
140 |
/* Loop through this - maybe we have several complete packets here. */ |
141 |
// limit to a few commands only, though, as to not monopolise the server |
142 |
for (int repeat = 16; repeat--;) |
143 |
{ |
144 |
/* If it is a player, and they don't have any speed left, we |
145 |
* return, and will read in the data when they do have time. |
146 |
*/ |
147 |
if (pl && pl->state == ST_PLAYING && pl->ob && pl->ob->speed_left < 0) |
148 |
return; |
149 |
|
150 |
int pkt_len = ns->read_packet (); |
151 |
|
152 |
if (pkt_len < 0) |
153 |
{ |
154 |
LOG (llevError, "read error on player %s\n", &pl->ob->name); |
155 |
/* Caller will take care of cleaning this up */ |
156 |
ns->status = Ns_Dead; |
157 |
return; |
158 |
} |
159 |
else if (pkt_len == 0) |
160 |
/* Still dont have a full packet */ |
161 |
return; |
162 |
|
163 |
/* First, break out beginning word. There are at least |
164 |
* a few commands that do not have any paremeters. If |
165 |
* we get such a command, don't worry about trying |
166 |
* to break it up. |
167 |
*/ |
168 |
int datalen; |
169 |
char *data = strchr ((char *)ns->inbuf + 2, ' '); |
170 |
|
171 |
if (data) |
172 |
{ |
173 |
*data++ = 0; |
174 |
datalen = pkt_len - (data - (char *)ns->inbuf); |
175 |
} |
176 |
else |
177 |
{ |
178 |
data = (char *)ns->inbuf + 2; // better read garbage than segfault |
179 |
datalen = 0; |
180 |
} |
181 |
|
182 |
ns->inbuf [pkt_len] = 0; /* Terminate buffer - useful for string data */ |
183 |
|
184 |
for (pkt_type *pkt = packets; pkt < packets + (sizeof (packets) / sizeof (packets[0])); ++pkt) |
185 |
if (!strcmp ((char *)ns->inbuf + 2, pkt->name)) |
186 |
{ |
187 |
if (pkt->may_execute (ns)) |
188 |
{ |
189 |
pkt->execute (ns, data, datalen); |
190 |
ns->skip_packet (pkt_len); |
191 |
//D//TODO not doing this causes random memory corruption |
192 |
if (pkt->flags & PF_IMMEDIATE) |
193 |
goto next_packet; |
194 |
return; |
195 |
} |
196 |
} |
197 |
|
198 |
/* If we get here, we didn't find a valid command. Logging |
199 |
* this might be questionable, because a broken client/malicious |
200 |
* user could certainly send a whole bunch of invalid commands. |
201 |
*/ |
202 |
LOG (llevDebug, "Bad command from client (%s)\n", ns->inbuf + 2); |
203 |
ns->skip_packet (pkt_len); |
204 |
next_packet: |
205 |
; |
206 |
} |
207 |
} |
208 |
|
209 |
void |
210 |
flush_sockets (void) |
211 |
{ |
212 |
for (sockvec::iterator i = client_sockets.begin (); i != client_sockets.end (); ++i) |
213 |
if ((*i)->status != Ns_Dead) |
214 |
(*i)->flush (); |
215 |
} |
216 |
|
217 |
/** |
218 |
* This checks the sockets for input, does the right thing. |
219 |
* |
220 |
* A bit of this code is grabbed out of socket.c |
221 |
* There are 2 lists we need to look through - init_sockets is a list |
222 |
* |
223 |
*/ |
224 |
void |
225 |
doeric_server (void) |
226 |
{ |
227 |
int i, pollret; |
228 |
fd_set tmp_read; |
229 |
struct sockaddr_in addr; |
230 |
socklen_t addrlen = sizeof (struct sockaddr); |
231 |
player *pl, *next; |
232 |
int maxfd = 0; |
233 |
|
234 |
#ifdef CS_LOGSTATS |
235 |
if ((time (NULL) - cst_lst.time_start) >= CS_LOGTIME) |
236 |
write_cs_stats (); |
237 |
#endif |
238 |
|
239 |
/* Go through the players. Let the loop set the next pl value, |
240 |
* since we may remove some |
241 |
*/ |
242 |
//TODO: must be handled cleanly elsewhere |
243 |
for (pl = first_player; pl; ) |
244 |
{ |
245 |
player *npl = pl->next; |
246 |
|
247 |
//TODO: must be handled cleanly elsewhere |
248 |
if (pl->socket->status == Ns_Dead) |
249 |
{ |
250 |
save_player (pl->ob, 0); |
251 |
|
252 |
if (!QUERY_FLAG (pl->ob, FLAG_REMOVED)) |
253 |
{ |
254 |
terminate_all_pets (pl->ob); |
255 |
pl->ob->remove (); |
256 |
} |
257 |
|
258 |
leave (pl, 1); |
259 |
final_free_player (pl); |
260 |
} |
261 |
|
262 |
pl = npl; |
263 |
} |
264 |
|
265 |
FD_ZERO (&tmp_read); |
266 |
|
267 |
for (sockvec::iterator i = client_sockets.begin (); i != client_sockets.end (); ) |
268 |
{ |
269 |
client_socket *s = *i; |
270 |
|
271 |
if (s->status == Ns_Dead) |
272 |
{ |
273 |
client_sockets.erase (i); |
274 |
delete s; |
275 |
} |
276 |
else |
277 |
{ |
278 |
if (s->fd > maxfd) maxfd = s->fd; |
279 |
|
280 |
FD_SET (s->fd, &tmp_read); |
281 |
|
282 |
++i; |
283 |
} |
284 |
} |
285 |
|
286 |
struct timeval timeout; |
287 |
|
288 |
timeout.tv_sec = 0; |
289 |
timeout.tv_usec = 0; |
290 |
|
291 |
pollret = select (maxfd + 1, |
292 |
&tmp_read, 0, 0, |
293 |
&timeout); |
294 |
|
295 |
if (pollret == -1) |
296 |
{ |
297 |
LOG (llevError, "select failed: %s\n", strerror (errno)); |
298 |
return; |
299 |
} |
300 |
|
301 |
/* We need to do some of the processing below regardless */ |
302 |
|
303 |
/* Check for any input on the sockets */ |
304 |
for (sockvec::iterator i = client_sockets.begin (); i != client_sockets.end (); ++i) |
305 |
{ |
306 |
client_socket *s = *i; |
307 |
player *pl = s->pl; |
308 |
|
309 |
//TODO: disassociate handleclient from socket readin |
310 |
if (s->inbuf_len || FD_ISSET (s->fd, &tmp_read)) |
311 |
HandleClient (s, pl); |
312 |
|
313 |
//TODO: should not be done here, either |
314 |
if (s->status != Ns_Dead && pl) |
315 |
{ |
316 |
/* Update the players stats once per tick. More efficient than |
317 |
* sending them whenever they change, and probably just as useful |
318 |
*/ |
319 |
esrv_update_stats (pl); |
320 |
if (pl->last_weight != -1 && pl->last_weight != WEIGHT (pl->ob)) |
321 |
{ |
322 |
esrv_update_item (UPD_WEIGHT, pl->ob, pl->ob); |
323 |
if (pl->last_weight != WEIGHT (pl->ob)) |
324 |
LOG (llevError, "esrv_update_item(UPD_WEIGHT) did not set player weight: is %lu, should be %lu\n", |
325 |
(unsigned long) pl->last_weight, WEIGHT (pl->ob)); |
326 |
} |
327 |
|
328 |
/* draw_client_map does sanity checking that map is |
329 |
* valid, so don't do it here. |
330 |
*/ |
331 |
draw_client_map (pl->ob); |
332 |
if (s->update_look) |
333 |
esrv_draw_look (pl->ob); |
334 |
} |
335 |
} |
336 |
} |
337 |
|