File: server\sv_user.c
1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20 // sv_user.c -- server code for moving users
21
22 #include "server.h"
23
24 edict_t *sv_player;
25
26 /*
27 ============================================================
28
29 USER STRINGCMD EXECUTION
30
31 sv_client and sv_player will be valid.
32 ============================================================
33 */
34
35 /*
36 ==================
37 SV_BeginDemoServer
38 ==================
39 */
40 void SV_BeginDemoserver (void)
41 {
42 char name[MAX_OSPATH];
43
44 Com_sprintf (name, sizeof(name), "demos/%s", sv.name);
45 FS_FOpenFile (name, &sv.demofile);
46 if (!sv.demofile)
47 Com_Error (ERR_DROP, "Couldn't open %s\n", name);
48 }
49
50 /*
51 ================
52 SV_New_f
53
54 Sends the first message from the server to a connected client.
55 This will be sent on the initial connection and upon each server load.
56 ================
57 */
58 void SV_New_f (void)
59 {
60 char *gamedir;
61 int playernum;
62 edict_t *ent;
63
64 Com_DPrintf ("New() from %s\n", sv_client->name);
65
66 if (sv_client->state != cs_connected)
67 {
68 Com_Printf ("New not valid -- already spawned\n");
69 return;
70 }
71
72 // demo servers just dump the file message
73 if (sv.state == ss_demo)
74 {
75 SV_BeginDemoserver ();
76 return;
77 }
78
79 //
80 // serverdata needs to go over for all types of servers
81 // to make sure the protocol is right, and to set the gamedir
82 //
83 gamedir = Cvar_VariableString ("gamedir");
84
85 // send the serverdata
86 MSG_WriteByte (&sv_client->netchan.message, svc_serverdata);
87 MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION);
88 MSG_WriteLong (&sv_client->netchan.message, svs.spawncount);
89 MSG_WriteByte (&sv_client->netchan.message, sv.attractloop);
90 MSG_WriteString (&sv_client->netchan.message, gamedir);
91
92 if (sv.state == ss_cinematic || sv.state == ss_pic)
93 playernum = -1;
94 else
95 playernum = sv_client - svs.clients;
96 MSG_WriteShort (&sv_client->netchan.message, playernum);
97
98 // send full levelname
99 MSG_WriteString (&sv_client->netchan.message, sv.configstrings[CS_NAME]);
100
101 //
102 // game server
103 //
104 if (sv.state == ss_game)
105 {
106 // set up the entity for the client
107 ent = EDICT_NUM(playernum+1);
108 ent->s.number = playernum+1;
109 sv_client->edict = ent;
110 memset (&sv_client->lastcmd, 0, sizeof(sv_client->lastcmd));
111
112 // begin fetching configstrings
113 MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
114 MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i 0\n",svs.spawncount) );
115 }
116
117 }
118
119 /*
120 ==================
121 SV_Configstrings_f
122 ==================
123 */
124 void SV_Configstrings_f (void)
125 {
126 int start;
127
128 Com_DPrintf ("Configstrings() from %s\n", sv_client->name);
129
130 if (sv_client->state != cs_connected)
131 {
132 Com_Printf ("configstrings not valid -- already spawned\n");
133 return;
134 }
135
136 // handle the case of a level changing while a client was connecting
137 if ( atoi(Cmd_Argv(1)) != svs.spawncount )
138 {
139 Com_Printf ("SV_Configstrings_f from different level\n");
140 SV_New_f ();
141 return;
142 }
143
144 start = atoi(Cmd_Argv(2));
145
146 // write a packet full of data
147
148 while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2
149 && start < MAX_CONFIGSTRINGS)
150 {
151 if (sv.configstrings[start][0])
152 {
153 MSG_WriteByte (&sv_client->netchan.message, svc_configstring);
154 MSG_WriteShort (&sv_client->netchan.message, start);
155 MSG_WriteString (&sv_client->netchan.message, sv.configstrings[start]);
156 }
157 start++;
158 }
159
160 // send next command
161
162 if (start == MAX_CONFIGSTRINGS)
163 {
164 MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
165 MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i 0\n",svs.spawncount) );
166 }
167 else
168 {
169 MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
170 MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i %i\n",svs.spawncount, start) );
171 }
172 }
173
174 /*
175 ==================
176 SV_Baselines_f
177 ==================
178 */
179 void SV_Baselines_f (void)
180 {
181 int start;
182 entity_state_t nullstate;
183 entity_state_t *base;
184
185 Com_DPrintf ("Baselines() from %s\n", sv_client->name);
186
187 if (sv_client->state != cs_connected)
188 {
189 Com_Printf ("baselines not valid -- already spawned\n");
190 return;
191 }
192
193 // handle the case of a level changing while a client was connecting
194 if ( atoi(Cmd_Argv(1)) != svs.spawncount )
195 {
196 Com_Printf ("SV_Baselines_f from different level\n");
197 SV_New_f ();
198 return;
199 }
200
201 start = atoi(Cmd_Argv(2));
202
203 memset (&nullstate, 0, sizeof(nullstate));
204
205 // write a packet full of data
206
207 while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2
208 && start < MAX_EDICTS)
209 {
210 base = &sv.baselines[start];
211 if (base->modelindex || base->sound || base->effects)
212 {
213 MSG_WriteByte (&sv_client->netchan.message, svc_spawnbaseline);
214 MSG_WriteDeltaEntity (&nullstate, base, &sv_client->netchan.message, true, true);
215 }
216 start++;
217 }
218
219 // send next command
220
221 if (start == MAX_EDICTS)
222 {
223 MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
224 MSG_WriteString (&sv_client->netchan.message, va("precache %i\n", svs.spawncount) );
225 }
226 else
227 {
228 MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
229 MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i %i\n",svs.spawncount, start) );
230 }
231 }
232
233 /*
234 ==================
235 SV_Begin_f
236 ==================
237 */
238 void SV_Begin_f (void)
239 {
240 Com_DPrintf ("Begin() from %s\n", sv_client->name);
241
242 // handle the case of a level changing while a client was connecting
243 if ( atoi(Cmd_Argv(1)) != svs.spawncount )
244 {
245 Com_Printf ("SV_Begin_f from different level\n");
246 SV_New_f ();
247 return;
248 }
249
250 sv_client->state = cs_spawned;
251
252 // call the game begin function
253 ge->ClientBegin (sv_player);
254
255 Cbuf_InsertFromDefer ();
256 }
257
258 //=============================================================================
259
260 /*
261 ==================
262 SV_NextDownload_f
263 ==================
264 */
265 void SV_NextDownload_f (void)
266 {
267 int r;
268 int percent;
269 int size;
270
271 if (!sv_client->download)
272 return;
273
274 r = sv_client->downloadsize - sv_client->downloadcount;
275 if (r > 1024)
276 r = 1024;
277
278 MSG_WriteByte (&sv_client->netchan.message, svc_download);
279 MSG_WriteShort (&sv_client->netchan.message, r);
280
281 sv_client->downloadcount += r;
282 size = sv_client->downloadsize;
283 if (!size)
284 size = 1;
285 percent = sv_client->downloadcount*100/size;
286 MSG_WriteByte (&sv_client->netchan.message, percent);
287 SZ_Write (&sv_client->netchan.message,
288 sv_client->download + sv_client->downloadcount - r, r);
289
290 if (sv_client->downloadcount != sv_client->downloadsize)
291 return;
292
293 FS_FreeFile (sv_client->download);
294 sv_client->download = NULL;
295 }
296
297 /*
298 ==================
299 SV_BeginDownload_f
300 ==================
301 */
302 void SV_BeginDownload_f(void)
303 {
304 char *name;
305 extern cvar_t *allow_download;
306 extern cvar_t *allow_download_players;
307 extern cvar_t *allow_download_models;
308 extern cvar_t *allow_download_sounds;
309 extern cvar_t *allow_download_maps;
310 extern int file_from_pak; // ZOID did file come from pak?
311 int offset = 0;
312
313 name = Cmd_Argv(1);
314
315 if (Cmd_Argc() > 2)
316 offset = atoi(Cmd_Argv(2)); // downloaded offset
317
318 // hacked by zoid to allow more conrol over download
319 // first off, no .. or global allow check
320 if (strstr (name, "..") || !allow_download->value
321 // leading dot is no good
322 || *name == '.'
323 // leading slash bad as well, must be in subdir
324 || *name == '/'
325 // next up, skin check
326 || (strncmp(name, "players/", 6) == 0 && !allow_download_players->value)
327 // now models
328 || (strncmp(name, "models/", 6) == 0 && !allow_download_models->value)
329 // now sounds
330 || (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value)
331 // now maps (note special case for maps, must not be in pak)
332 || (strncmp(name, "maps/", 6) == 0 && !allow_download_maps->value)
333 // MUST be in a subdirectory
334 || !strstr (name, "/") )
335 { // don't allow anything with .. path
336 MSG_WriteByte (&sv_client->netchan.message, svc_download);
337 MSG_WriteShort (&sv_client->netchan.message, -1);
338 MSG_WriteByte (&sv_client->netchan.message, 0);
339 return;
340 }
341
342
343 if (sv_client->download)
344 FS_FreeFile (sv_client->download);
345
346 sv_client->downloadsize = FS_LoadFile (name, (void **)&sv_client->download);
347 sv_client->downloadcount = offset;
348
349 if (offset > sv_client->downloadsize)
350 sv_client->downloadcount = sv_client->downloadsize;
351
352 if (!sv_client->download
353 // special check for maps, if it came from a pak file, don't allow
354 // download ZOID
355 || (strncmp(name, "maps/", 5) == 0 && file_from_pak))
356 {
357 Com_DPrintf ("Couldn't download %s to %s\n", name, sv_client->name);
358 if (sv_client->download) {
359 FS_FreeFile (sv_client->download);
360 sv_client->download = NULL;
361 }
362
363 MSG_WriteByte (&sv_client->netchan.message, svc_download);
364 MSG_WriteShort (&sv_client->netchan.message, -1);
365 MSG_WriteByte (&sv_client->netchan.message, 0);
366 return;
367 }
368
369 SV_NextDownload_f ();
370 Com_DPrintf ("Downloading %s to %s\n", name, sv_client->name);
371 }
372
373
374
375 //============================================================================
376
377
378 /*
379 =================
380 SV_Disconnect_f
381
382 The client is going to disconnect, so remove the connection immediately
383 =================
384 */
385 void SV_Disconnect_f (void)
386 {
387 // SV_EndRedirect ();
388 SV_DropClient (sv_client);
389 }
390
391
392 /*
393 ==================
394 SV_ShowServerinfo_f
395
396 Dumps the serverinfo info string
397 ==================
398 */
399 void SV_ShowServerinfo_f (void)
400 {
401 Info_Print (Cvar_Serverinfo());
402 }
403
404
405 void SV_Nextserver (void)
406 {
407 char *v;
408
409 //ZOID, ss_pic can be nextserver'd in coop mode
410 if (sv.state == ss_game || (sv.state == ss_pic && !Cvar_VariableValue("coop")))
411 return; // can't nextserver while playing a normal game
412
413 svs.spawncount++; // make sure another doesn't sneak in
414 v = Cvar_VariableString ("nextserver");
415 if (!v[0])
416 Cbuf_AddText ("killserver\n");
417 else
418 {
419 Cbuf_AddText (v);
420 Cbuf_AddText ("\n");
421 }
422 Cvar_Set ("nextserver","");
423 }
424
425 /*
426 ==================
427 SV_Nextserver_f
428
429 A cinematic has completed or been aborted by a client, so move
430 to the next server,
431 ==================
432 */
433 void SV_Nextserver_f (void)
434 {
435 if ( atoi(Cmd_Argv(1)) != svs.spawncount ) {
436 Com_DPrintf ("Nextserver() from wrong level, from %s\n", sv_client->name);
437 return; // leftover from last server
438 }
439
440 Com_DPrintf ("Nextserver() from %s\n", sv_client->name);
441
442 SV_Nextserver ();
443 }
444
445 typedef struct
446 {
447 char *name;
448 void (*func) (void);
449 } ucmd_t;
450
451 ucmd_t ucmds[] =
452 {
453 // auto issued
454 {"new", SV_New_f},
455 {"configstrings", SV_Configstrings_f},
456 {"baselines", SV_Baselines_f},
457 {"begin", SV_Begin_f},
458
459 {"nextserver", SV_Nextserver_f},
460
461 {"disconnect", SV_Disconnect_f},
462
463 // issued by hand at client consoles
464 {"info", SV_ShowServerinfo_f},
465
466 {"download", SV_BeginDownload_f},
467 {"nextdl", SV_NextDownload_f},
468
469 {NULL, NULL}
470 };
471
472 /*
473 ==================
474 SV_ExecuteUserCommand
475 ==================
476 */
477 void SV_ExecuteUserCommand (char *s)
478 {
479 ucmd_t *u;
480
481 Cmd_TokenizeString (s, true);
482 sv_player = sv_client->edict;
483
484 // SV_BeginRedirect (RD_CLIENT);
485
486 for (u=ucmds ; u->name ; u++)
487 if (!strcmp (Cmd_Argv(0), u->name) )
488 {
489 u->func ();
490 break;
491 }
492
493 if (!u->name && sv.state == ss_game)
494 ge->ClientCommand (sv_player);
495
496 // SV_EndRedirect ();
497 }
498
499 /*
500 ===========================================================================
501
502 USER CMD EXECUTION
503
504 ===========================================================================
505 */
506
507
508
509 void SV_ClientThink (client_t *cl, usercmd_t *cmd)
510
511 {
512 cl->commandMsec -= cmd->msec;
513
514 if (cl->commandMsec < 0 && sv_enforcetime->value )
515 {
516 Com_DPrintf ("commandMsec underflow from %s\n", cl->name);
517 return;
518 }
519
520 ge->ClientThink (cl->edict, cmd);
521 }
522
523
524
525 #define MAX_STRINGCMDS 8
526 /*
527 ===================
528 SV_ExecuteClientMessage
529
530 The current net_message is parsed for the given client
531 ===================
532 */
533 void SV_ExecuteClientMessage (client_t *cl)
534 {
535 int c;
536 char *s;
537
538 usercmd_t nullcmd;
539 usercmd_t oldest, oldcmd, newcmd;
540 int net_drop;
541 int stringCmdCount;
542 int checksum, calculatedChecksum;
543 int checksumIndex;
544 qboolean move_issued;
545 int lastframe;
546
547 sv_client = cl;
548 sv_player = sv_client->edict;
549
550 // only allow one move command
551 move_issued = false;
552 stringCmdCount = 0;
553
554 while (1)
555 {
556 if (net_message.readcount > net_message.cursize)
557 {
558 Com_Printf ("SV_ReadClientMessage: badread\n");
559 SV_DropClient (cl);
560 return;
561 }
562
563 c = MSG_ReadByte (&net_message);
564 if (c == -1)
565 break;
566
567 switch (c)
568 {
569 default:
570 Com_Printf ("SV_ReadClientMessage: unknown command char\n");
571 SV_DropClient (cl);
572 return;
573
574 case clc_nop:
575 break;
576
577 case clc_userinfo:
578 strncpy (cl->userinfo, MSG_ReadString (&net_message), sizeof(cl->userinfo)-1);
579 SV_UserinfoChanged (cl);
580 break;
581
582 case clc_move:
583 if (move_issued)
584 return; // someone is trying to cheat...
585
586 move_issued = true;
587 checksumIndex = net_message.readcount;
588 checksum = MSG_ReadByte (&net_message);
589 lastframe = MSG_ReadLong (&net_message);
590 if (lastframe != cl->lastframe) {
591 cl->lastframe = lastframe;
592 if (cl->lastframe > 0) {
593 cl->frame_latency[cl->lastframe&(LATENCY_COUNTS-1)] =
594 svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime;
595 }
596 }
597
598 memset (&nullcmd, 0, sizeof(nullcmd));
599 MSG_ReadDeltaUsercmd (&net_message, &nullcmd, &oldest);
600 MSG_ReadDeltaUsercmd (&net_message, &oldest, &oldcmd);
601 MSG_ReadDeltaUsercmd (&net_message, &oldcmd, &newcmd);
602
603 if ( cl->state != cs_spawned )
604 {
605 cl->lastframe = -1;
606 break;
607 }
608
609 // if the checksum fails, ignore the rest of the packet
610 calculatedChecksum = COM_BlockSequenceCRCByte (
611 net_message.data + checksumIndex + 1,
612 net_message.readcount - checksumIndex - 1,
613 cl->netchan.incoming_sequence);
614
615 if (calculatedChecksum != checksum)
616 {
617 Com_DPrintf ("Failed command checksum for %s (%d != %d)/%d\n",
618 cl->name, calculatedChecksum, checksum,
619 cl->netchan.incoming_sequence);
620 return;
621 }
622
623 if (!sv_paused->value)
624 {
625 net_drop = cl->netchan.dropped;
626 if (net_drop < 20)
627 {
628
629 //if (net_drop > 2)
630
631 // Com_Printf ("drop %i\n", net_drop);
632 while (net_drop > 2)
633 {
634 SV_ClientThink (cl, &cl->lastcmd);
635
636 net_drop--;
637 }
638 if (net_drop > 1)
639 SV_ClientThink (cl, &oldest);
640
641 if (net_drop > 0)
642 SV_ClientThink (cl, &oldcmd);
643
644 }
645 SV_ClientThink (cl, &newcmd);
646 }
647
648 cl->lastcmd = newcmd;
649 break;
650
651 case clc_stringcmd:
652 s = MSG_ReadString (&net_message);
653
654 // malicious users may try using too many string commands
655 if (++stringCmdCount < MAX_STRINGCMDS)
656 SV_ExecuteUserCommand (s);
657
658 if (cl->state == cs_zombie)
659 return; // disconnect command
660 break;
661 }
662 }
663 }
664
665