Home | History | Annotate | Download | only in client
      1 /*
      2 Copyright (C) 1996-1997 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 included (GNU.txt) 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 /* ZOID
     21  *
     22  * Player camera tracking in Spectator mode
     23  *
     24  * This takes over player controls for spectator automatic camera.
     25  * Player moves as a spectator, but the camera tracks and enemy player
     26  */
     27 
     28 #include "quakedef.h"
     29 #include "winquake.h"
     30 
     31 #define	PM_SPECTATORMAXSPEED	500
     32 #define	PM_STOPSPEED	100
     33 #define	PM_MAXSPEED			320
     34 #define BUTTON_JUMP 2
     35 #define BUTTON_ATTACK 1
     36 #define MAX_ANGLE_TURN 10
     37 
     38 static vec3_t desired_position; // where the camera wants to be
     39 static qboolean locked = false;
     40 static int oldbuttons;
     41 
     42 // track high fragger
     43 cvar_t cl_hightrack = CVAR2("cl_hightrack", "0" );
     44 
     45 cvar_t cl_chasecam = CVAR2("cl_chasecam", "0");
     46 
     47 //cvar_t cl_camera_maxpitch = {"cl_camera_maxpitch", "10" };
     48 //cvar_t cl_camera_maxyaw = {"cl_camera_maxyaw", "30" };
     49 
     50 qboolean cam_forceview;
     51 vec3_t cam_viewangles;
     52 double cam_lastviewtime;
     53 
     54 int spec_track = 0; // player# of who we are tracking
     55 int autocam = CAM_NONE;
     56 
     57 static void vectoangles(vec3_t vec, vec3_t ang)
     58 {
     59 	float	forward;
     60 	float	yaw, pitch;
     61 
     62 	if (vec[1] == 0 && vec[0] == 0)
     63 	{
     64 		yaw = 0;
     65 		if (vec[2] > 0)
     66 			pitch = 90;
     67 		else
     68 			pitch = 270;
     69 	}
     70 	else
     71 	{
     72 		yaw = (int) (atan2(vec[1], vec[0]) * 180 / M_PI);
     73 		if (yaw < 0)
     74 			yaw += 360;
     75 
     76 		forward = sqrt (vec[0]*vec[0] + vec[1]*vec[1]);
     77 		pitch = (int) (atan2(vec[2], forward) * 180 / M_PI);
     78 		if (pitch < 0)
     79 			pitch += 360;
     80 	}
     81 
     82 	ang[0] = pitch;
     83 	ang[1] = yaw;
     84 	ang[2] = 0;
     85 }
     86 
     87 static float vlen(vec3_t v)
     88 {
     89 	return sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
     90 }
     91 
     92 // returns true if weapon model should be drawn in camera mode
     93 qboolean Cam_DrawViewModel(void)
     94 {
     95 	if (!cl.spectator)
     96 		return true;
     97 
     98 	if (autocam && locked && cl_chasecam.value)
     99 		return true;
    100 	return false;
    101 }
    102 
    103 // returns true if we should draw this player, we don't if we are chase camming
    104 qboolean Cam_DrawPlayer(int playernum)
    105 {
    106 	if (cl.spectator && autocam && locked && cl_chasecam.value &&
    107 		spec_track == playernum)
    108 		return false;
    109 	return true;
    110 }
    111 
    112 void Cam_Unlock(void)
    113 {
    114 	if (autocam) {
    115 		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
    116 		MSG_WriteString (&cls.netchan.message, "ptrack");
    117 		autocam = CAM_NONE;
    118 		locked = false;
    119 		Sbar_Changed();
    120 	}
    121 }
    122 
    123 void Cam_Lock(int playernum)
    124 {
    125 	char st[40];
    126 
    127 	sprintf(st, "ptrack %i", playernum);
    128 	MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
    129 	MSG_WriteString (&cls.netchan.message, st);
    130 	spec_track = playernum;
    131 	cam_forceview = true;
    132 	locked = false;
    133 	Sbar_Changed();
    134 }
    135 
    136 pmtrace_t Cam_DoTrace(vec3_t vec1, vec3_t vec2)
    137 {
    138 #if 0
    139 	memset(&pmove, 0, sizeof(pmove));
    140 
    141 	pmove.numphysent = 1;
    142 	VectorCopy (vec3_origin, pmove.physents[0].origin);
    143 	pmove.physents[0].model = cl.worldmodel;
    144 #endif
    145 
    146 	VectorCopy (vec1, pmove.origin);
    147 	return PM_PlayerMove(pmove.origin, vec2);
    148 }
    149 
    150 // Returns distance or 9999 if invalid for some reason
    151 static float Cam_TryFlyby(player_state_t *self, player_state_t *player, vec3_t vec, qboolean checkvis)
    152 {
    153 	vec3_t v;
    154 	pmtrace_t trace;
    155 	float len;
    156 
    157 	vectoangles(vec, v);
    158 //	v[0] = -v[0];
    159 	VectorCopy (v, pmove.angles);
    160 	VectorNormalize(vec);
    161 	VectorMA(player->origin, 800, vec, v);
    162 	// v is endpos
    163 	// fake a player move
    164 	trace = Cam_DoTrace(player->origin, v);
    165 	if (/*trace.inopen ||*/ trace.inwater)
    166 		return 9999;
    167 	VectorCopy(trace.endpos, vec);
    168 	VectorSubtract(trace.endpos, player->origin, v);
    169 	len = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
    170 	if (len < 32 || len > 800)
    171 		return 9999;
    172 	if (checkvis) {
    173 		VectorSubtract(trace.endpos, self->origin, v);
    174 		len = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
    175 
    176 		trace = Cam_DoTrace(self->origin, vec);
    177 		if (trace.fraction != 1 || trace.inwater)
    178 			return 9999;
    179 	}
    180 	return len;
    181 }
    182 
    183 // Is player visible?
    184 static qboolean Cam_IsVisible(player_state_t *player, vec3_t vec)
    185 {
    186 	pmtrace_t trace;
    187 	vec3_t v;
    188 	float d;
    189 
    190 	trace = Cam_DoTrace(player->origin, vec);
    191 	if (trace.fraction != 1 || /*trace.inopen ||*/ trace.inwater)
    192 		return false;
    193 	// check distance, don't let the player get too far away or too close
    194 	VectorSubtract(player->origin, vec, v);
    195 	d = vlen(v);
    196 	if (d < 16)
    197 		return false;
    198 	return true;
    199 }
    200 
    201 static qboolean InitFlyby(player_state_t *self, player_state_t *player, int checkvis)
    202 {
    203     float f, max;
    204     vec3_t vec, vec2;
    205 	vec3_t forward, right, up;
    206 
    207 	VectorCopy(player->viewangles, vec);
    208     vec[0] = 0;
    209 	AngleVectors (vec, forward, right, up);
    210 //	for (i = 0; i < 3; i++)
    211 //		forward[i] *= 3;
    212 
    213     max = 1000;
    214 	VectorAdd(forward, up, vec2);
    215 	VectorAdd(vec2, right, vec2);
    216     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    217         max = f;
    218 		VectorCopy(vec2, vec);
    219     }
    220 	VectorAdd(forward, up, vec2);
    221 	VectorSubtract(vec2, right, vec2);
    222     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    223         max = f;
    224 		VectorCopy(vec2, vec);
    225     }
    226 	VectorAdd(forward, right, vec2);
    227     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    228         max = f;
    229 		VectorCopy(vec2, vec);
    230     }
    231 	VectorSubtract(forward, right, vec2);
    232     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    233         max = f;
    234 		VectorCopy(vec2, vec);
    235     }
    236 	VectorAdd(forward, up, vec2);
    237     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    238         max = f;
    239 		VectorCopy(vec2, vec);
    240     }
    241 	VectorSubtract(forward, up, vec2);
    242     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    243         max = f;
    244 		VectorCopy(vec2, vec);
    245     }
    246 	VectorAdd(up, right, vec2);
    247 	VectorSubtract(vec2, forward, vec2);
    248     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    249         max = f;
    250 		VectorCopy(vec2, vec);
    251     }
    252 	VectorSubtract(up, right, vec2);
    253 	VectorSubtract(vec2, forward, vec2);
    254     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    255         max = f;
    256 		VectorCopy(vec2, vec);
    257     }
    258 	// invert
    259 	VectorSubtract(vec3_origin, forward, vec2);
    260     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    261         max = f;
    262 		VectorCopy(vec2, vec);
    263     }
    264 	VectorCopy(forward, vec2);
    265     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    266         max = f;
    267 		VectorCopy(vec2, vec);
    268     }
    269 	// invert
    270 	VectorSubtract(vec3_origin, right, vec2);
    271     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    272         max = f;
    273 		VectorCopy(vec2, vec);
    274     }
    275 	VectorCopy(right, vec2);
    276     if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {
    277         max = f;
    278 		VectorCopy(vec2, vec);
    279     }
    280 
    281 	// ack, can't find him
    282     if (max >= 1000) {
    283 //		Cam_Unlock();
    284 		return false;
    285 	}
    286 	locked = true;
    287 	VectorCopy(vec, desired_position);
    288 	return true;
    289 }
    290 
    291 static void Cam_CheckHighTarget(void)
    292 {
    293 	int i, j, max;
    294 	player_info_t	*s;
    295 
    296 	j = -1;
    297 	for (i = 0, max = -9999; i < MAX_CLIENTS; i++) {
    298 		s = &cl.players[i];
    299 		if (s->name[0] && !s->spectator && s->frags > max) {
    300 			max = s->frags;
    301 			j = i;
    302 		}
    303 	}
    304 	if (j >= 0) {
    305 		if (!locked || cl.players[j].frags > cl.players[spec_track].frags)
    306 			Cam_Lock(j);
    307 	} else
    308 		Cam_Unlock();
    309 }
    310 
    311 // ZOID
    312 //
    313 // Take over the user controls and track a player.
    314 // We find a nice position to watch the player and move there
    315 void Cam_Track(usercmd_t *cmd)
    316 {
    317 	player_state_t *player, *self;
    318 	frame_t *frame;
    319 	vec3_t vec;
    320 	float len;
    321 
    322 	if (!cl.spectator)
    323 		return;
    324 
    325 	if (cl_hightrack.value && !locked)
    326 		Cam_CheckHighTarget();
    327 
    328 	if (!autocam || cls.state != ca_active)
    329 		return;
    330 
    331 	if (locked && (!cl.players[spec_track].name[0] || cl.players[spec_track].spectator)) {
    332 		locked = false;
    333 		if (cl_hightrack.value)
    334 			Cam_CheckHighTarget();
    335 		else
    336 			Cam_Unlock();
    337 		return;
    338 	}
    339 
    340 	frame = &cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK];
    341 	player = frame->playerstate + spec_track;
    342 	self = frame->playerstate + cl.playernum;
    343 
    344 	if (!locked || !Cam_IsVisible(player, desired_position)) {
    345 		if (!locked || realtime - cam_lastviewtime > 0.1) {
    346 			if (!InitFlyby(self, player, true))
    347 				InitFlyby(self, player, false);
    348 			cam_lastviewtime = realtime;
    349 		}
    350 	} else
    351 		cam_lastviewtime = realtime;
    352 
    353 	// couldn't track for some reason
    354 	if (!locked || !autocam)
    355 		return;
    356 
    357 	if (cl_chasecam.value) {
    358 		cmd->forwardmove = cmd->sidemove = cmd->upmove = 0;
    359 
    360 		VectorCopy(player->viewangles, cl.viewangles);
    361 		VectorCopy(player->origin, desired_position);
    362 		if (memcmp(&desired_position, &self->origin, sizeof(desired_position)) != 0) {
    363 			MSG_WriteByte (&cls.netchan.message, clc_tmove);
    364 			MSG_WriteCoord (&cls.netchan.message, desired_position[0]);
    365 			MSG_WriteCoord (&cls.netchan.message, desired_position[1]);
    366 			MSG_WriteCoord (&cls.netchan.message, desired_position[2]);
    367 			// move there locally immediately
    368 			VectorCopy(desired_position, self->origin);
    369 		}
    370 		self->weaponframe = player->weaponframe;
    371 
    372 	} else {
    373 		// Ok, move to our desired position and set our angles to view
    374 		// the player
    375 		VectorSubtract(desired_position, self->origin, vec);
    376 		len = vlen(vec);
    377 		cmd->forwardmove = cmd->sidemove = cmd->upmove = 0;
    378 		if (len > 16) { // close enough?
    379 			MSG_WriteByte (&cls.netchan.message, clc_tmove);
    380 			MSG_WriteCoord (&cls.netchan.message, desired_position[0]);
    381 			MSG_WriteCoord (&cls.netchan.message, desired_position[1]);
    382 			MSG_WriteCoord (&cls.netchan.message, desired_position[2]);
    383 		}
    384 
    385 		// move there locally immediately
    386 		VectorCopy(desired_position, self->origin);
    387 
    388 		VectorSubtract(player->origin, desired_position, vec);
    389 		vectoangles(vec, cl.viewangles);
    390 		cl.viewangles[0] = -cl.viewangles[0];
    391 	}
    392 }
    393 
    394 #if 0
    395 static float adjustang(float current, float ideal, float speed)
    396 {
    397 	float move;
    398 
    399 	current = anglemod(current);
    400 	ideal = anglemod(ideal);
    401 
    402 	if (current == ideal)
    403 		return current;
    404 
    405 	move = ideal - current;
    406 	if (ideal > current)
    407 	{
    408 		if (move >= 180)
    409 			move = move - 360;
    410 	}
    411 	else
    412 	{
    413 		if (move <= -180)
    414 			move = move + 360;
    415 	}
    416 	if (move > 0)
    417 	{
    418 		if (move > speed)
    419 			move = speed;
    420 	}
    421 	else
    422 	{
    423 		if (move < -speed)
    424 			move = -speed;
    425 	}
    426 
    427 //Con_Printf("c/i: %4.2f/%4.2f move: %4.2f\n", current, ideal, move);
    428 	return anglemod (current + move);
    429 }
    430 #endif
    431 
    432 #if 0
    433 void Cam_SetView(void)
    434 {
    435 	return;
    436 	player_state_t *player, *self;
    437 	frame_t *frame;
    438 	vec3_t vec, vec2;
    439 
    440 	if (cls.state != ca_active || !cl.spectator ||
    441 		!autocam || !locked)
    442 		return;
    443 
    444 	frame = &cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK];
    445 	player = frame->playerstate + spec_track;
    446 	self = frame->playerstate + cl.playernum;
    447 
    448 	VectorSubtract(player->origin, cl.simorg, vec);
    449 	if (cam_forceview) {
    450 		cam_forceview = false;
    451 		vectoangles(vec, cam_viewangles);
    452 		cam_viewangles[0] = -cam_viewangles[0];
    453 	} else {
    454 		vectoangles(vec, vec2);
    455 		vec2[PITCH] = -vec2[PITCH];
    456 
    457 		cam_viewangles[PITCH] = adjustang(cam_viewangles[PITCH], vec2[PITCH], cl_camera_maxpitch.value);
    458 		cam_viewangles[YAW] = adjustang(cam_viewangles[YAW], vec2[YAW], cl_camera_maxyaw.value);
    459 	}
    460 	VectorCopy(cam_viewangles, cl.viewangles);
    461 	VectorCopy(cl.viewangles, cl.simangles);
    462 }
    463 #endif
    464 
    465 void Cam_FinishMove(usercmd_t *cmd)
    466 {
    467 	int i;
    468 	player_info_t	*s;
    469 	int end;
    470 
    471 	if (cls.state != ca_active)
    472 		return;
    473 
    474 	if (!cl.spectator) // only in spectator mode
    475 		return;
    476 
    477 #if 0
    478 	if (autocam && locked) {
    479 		frame = &cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK];
    480 		player = frame->playerstate + spec_track;
    481 		self = frame->playerstate + cl.playernum;
    482 
    483 		VectorSubtract(player->origin, self->origin, vec);
    484 		if (cam_forceview) {
    485 			cam_forceview = false;
    486 			vectoangles(vec, cam_viewangles);
    487 			cam_viewangles[0] = -cam_viewangles[0];
    488 		} else {
    489 			vectoangles(vec, vec2);
    490 			vec2[PITCH] = -vec2[PITCH];
    491 
    492 			cam_viewangles[PITCH] = adjustang(cam_viewangles[PITCH], vec2[PITCH], cl_camera_maxpitch.value);
    493 			cam_viewangles[YAW] = adjustang(cam_viewangles[YAW], vec2[YAW], cl_camera_maxyaw.value);
    494 		}
    495 		VectorCopy(cam_viewangles, cl.viewangles);
    496 	}
    497 #endif
    498 
    499 	if (cmd->buttons & BUTTON_ATTACK) {
    500 		if (!(oldbuttons & BUTTON_ATTACK)) {
    501 
    502 			oldbuttons |= BUTTON_ATTACK;
    503 			autocam++;
    504 
    505 			if (autocam > CAM_TRACK) {
    506 				Cam_Unlock();
    507 				VectorCopy(cl.viewangles, cmd->angles);
    508 				return;
    509 			}
    510 		} else
    511 			return;
    512 	} else {
    513 		oldbuttons &= ~BUTTON_ATTACK;
    514 		if (!autocam)
    515 			return;
    516 	}
    517 
    518 	if (autocam && cl_hightrack.value) {
    519 		Cam_CheckHighTarget();
    520 		return;
    521 	}
    522 
    523 	if (locked) {
    524 		if ((cmd->buttons & BUTTON_JUMP) && (oldbuttons & BUTTON_JUMP))
    525 			return;		// don't pogo stick
    526 
    527 		if (!(cmd->buttons & BUTTON_JUMP)) {
    528 			oldbuttons &= ~BUTTON_JUMP;
    529 			return;
    530 		}
    531 		oldbuttons |= BUTTON_JUMP;	// don't jump again until released
    532 	}
    533 
    534 //	Con_Printf("Selecting track target...\n");
    535 
    536 	if (locked && autocam)
    537 		end = (spec_track + 1) % MAX_CLIENTS;
    538 	else
    539 		end = spec_track;
    540 	i = end;
    541 	do {
    542 		s = &cl.players[i];
    543 		if (s->name[0] && !s->spectator) {
    544 			Cam_Lock(i);
    545 			return;
    546 		}
    547 		i = (i + 1) % MAX_CLIENTS;
    548 	} while (i != end);
    549 	// stay on same guy?
    550 	i = spec_track;
    551 	s = &cl.players[i];
    552 	if (s->name[0] && !s->spectator) {
    553 		Cam_Lock(i);
    554 		return;
    555 	}
    556 	Con_Printf("No target found ...\n");
    557 	autocam = locked = false;
    558 }
    559 
    560 void Cam_Reset(void)
    561 {
    562 	autocam = CAM_NONE;
    563 	spec_track = 0;
    564 }
    565 
    566 void CL_InitCam(void)
    567 {
    568 	Cvar_RegisterVariable (&cl_hightrack);
    569 	Cvar_RegisterVariable (&cl_chasecam);
    570 //	Cvar_RegisterVariable (&cl_camera_maxpitch);
    571 //	Cvar_RegisterVariable (&cl_camera_maxyaw);
    572 }
    573 
    574 
    575