Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

sv_user.c (15042B)


      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