1 /*
2 	cl_cam.c
3 
4 	Player camera tracking in Spectator mode
5 
6 	Copyright (C) 1996-1997  Id Software, Inc.
7 
8 	This program is free software; you can redistribute it and/or
9 	modify it under the terms of the GNU General Public License
10 	as published by the Free Software Foundation; either version 2
11 	of the License, or (at your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 	See the GNU General Public License for more details.
18 
19 	You should have received a copy of the GNU General Public License
20 	along with this program; if not, write to:
21 
22 		Free Software Foundation, Inc.
23 		59 Temple Place - Suite 330
24 		Boston, MA  02111-1307, USA
25 
26 */
27 /*
28   ZOID - This takes over player controls for spectator automatic camera.
29   Player moves as a spectator, but the camera tracks an enemy player
30 */
31 
32 #ifdef HAVE_CONFIG_H
33 # include "config.h"
34 #endif
35 
36 #ifdef HAVE_STRING_H
37 # include <string.h>
38 #endif
39 #ifdef HAVE_STRINGS_H
40 # include <strings.h>
41 #endif
42 
43 #include <math.h>
44 
45 #include "QF/cvar.h"
46 #include "QF/msg.h"
47 
48 #include "chase.h"
49 #include "cl_cam.h"
50 #include "cl_input.h"
51 #include "client.h"
52 #include "compat.h"
53 #include "qw/pmove.h"
54 #include "sbar.h"
55 
56 #define	PM_SPECTATORMAXSPEED 500
57 #define	PM_STOPSPEED 100
58 #define	PM_MAXSPEED 320
59 #define BUTTON_JUMP 2
60 #define BUTTON_ATTACK 1
61 #define MAX_ANGLE_TURN 10
62 
63 #include "QF/sys.h"
64 #include "QF/keys.h"
65 #include "QF/input.h"
66 #include "QF/mathlib.h"
67 #include "world.h"
68 
69 cvar_t     *cl_hightrack;	// track high fragger
70 cvar_t     *cl_chasecam;
71 cvar_t     *cl_camera_maxpitch;
72 cvar_t     *cl_camera_maxyaw;
73 
74 static vec3_t desired_position;			// where the camera wants to be
75 static qboolean locked = false;
76 static int  oldbuttons;
77 
78 double      cam_lastviewtime;
79 qboolean    cam_forceview;
80 vec3_t      cam_viewangles;
81 
82 int         spec_track = 0;				// player# of who we are tracking
83 int         ideal_track = 0;
84 float       last_lock = 0;
85 int         autocam = CAM_NONE;
86 
87 
88 static void
vectoangles(vec3_t vec,vec3_t ang)89 vectoangles (vec3_t vec, vec3_t ang)
90 {
91 	float		forward, pitch, yaw;
92 
93 	if (vec[1] == 0 && vec[0] == 0) {
94 		yaw = 0;
95 		if (vec[2] > 0)
96 			pitch = 90;
97 		else
98 			pitch = 270;
99 	} else {
100 		yaw = (int) (atan2 (vec[1], vec[0]) * (180.0 / M_PI));
101 		if (yaw < 0)
102 			yaw += 360;
103 
104 		forward = sqrt (vec[0] * vec[0] + vec[1] * vec[1]);
105 		pitch = (int) (atan2 (vec[2], forward) * (180.0 / M_PI));
106 		if (pitch < 0)
107 			pitch += 360;
108 	}
109 
110 	ang[0] = pitch;
111 	ang[1] = yaw;
112 	ang[2] = 0;
113 }
114 
115 // returns true if weapon model should be drawn in camera mode
116 qboolean
Cam_DrawViewModel(void)117 Cam_DrawViewModel (void)
118 {
119 	if (cl.chase && chase_active->int_val)
120 		return false;
121 
122 	if (!cl.spectator)
123 		return true;
124 
125 	if (autocam && locked && cl_chasecam->int_val)
126 		return true;
127 	return false;
128 }
129 
130 // returns true if we should draw this player, we don't if we are chase camming
131 qboolean
Cam_DrawPlayer(int playernum)132 Cam_DrawPlayer (int playernum)
133 {
134 	if (playernum == cl.playernum) {						// client player
135 		if (cl.chase == 0 || chase_active->int_val == 0)
136 			return false;
137 		if (!cl.spectator)
138 			return true;
139 	} else {
140 		if (!cl_chasecam->int_val)
141 			return true;
142 		if (cl.spectator && autocam && locked && spec_track == playernum)
143 			return false;
144 		if (cl.chase == 0 || chase_active->int_val == 0)
145 			return true;
146 	}
147 	return false;
148 }
149 
150 int
Cam_TrackNum(void)151 Cam_TrackNum (void)
152 {
153 	if (!autocam)
154 		return -1;
155 	return spec_track;
156 }
157 
158 static void
Cam_Unlock(void)159 Cam_Unlock (void)
160 {
161 	if (autocam) {
162 		if (!cls.demoplayback) {
163 			MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
164 			MSG_WriteString (&cls.netchan.message, "ptrack");
165 		}
166 		autocam = CAM_NONE;
167 		locked = false;
168 		Sbar_Changed ();
169 	}
170 }
171 
172 void
Cam_Lock(int playernum)173 Cam_Lock (int playernum)
174 {
175 	char		st[40];
176 
177 	snprintf (st, sizeof (st), "ptrack %i", playernum);
178 	if (cls.demoplayback2) {
179 		memcpy (cl.stats, cl.players[playernum].stats, sizeof (cl.stats));
180 	}
181 
182 	if (!cls.demoplayback) {
183 		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
184 		MSG_WriteString (&cls.netchan.message, st);
185 	}
186 	spec_track = playernum;
187 	last_lock = realtime;
188 	cam_forceview = true;
189 	locked = false;
190 	Sbar_Changed ();
191 }
192 
193 static trace_t
Cam_DoTrace(vec3_t vec1,vec3_t vec2)194 Cam_DoTrace (vec3_t vec1, vec3_t vec2)
195 {
196 #if 0
197 	memset (&pmove, 0, sizeof (pmove));
198 
199 	pmove.numphysent = 1;
200 	VectorZero (pmove.physents[0].origin);
201 	pmove.physents[0].model = cl.worldmodel;
202 #endif
203 
204 	VectorCopy (vec1, pmove.origin);
205 	return PM_PlayerMove (pmove.origin, vec2);
206 }
207 
208 // Returns distance or 9999 if invalid for some reason
209 static float
Cam_TryFlyby(player_state_t * self,player_state_t * player,vec3_t vec,qboolean checkvis)210 Cam_TryFlyby (player_state_t * self, player_state_t * player, vec3_t vec,
211 			  qboolean checkvis)
212 {
213 	float       len;
214 	trace_t     trace;
215 	vec3_t      v;
216 
217 	vectoangles (vec, v);
218 	VectorCopy (v, pmove.angles);
219 	VectorNormalize (vec);
220 	VectorMultAdd (player->pls.origin, 800, vec, v);
221 	// v is endpos
222 	// fake a player move
223 	trace = Cam_DoTrace (player->pls.origin, v);
224 	if ( /* trace.inopen || */ trace.inwater)
225 		return 9999;
226 	VectorCopy (trace.endpos, vec);
227 	len = VectorDistance (trace.endpos, player->pls.origin);
228 
229 	if (len < 32 || len > 800)
230 		return 9999;
231 	if (checkvis) {
232 		trace = Cam_DoTrace (self->pls.origin, vec);
233 		if (trace.fraction != 1 || trace.inwater)
234 			return 9999;
235 
236 		len = VectorDistance (trace.endpos, self->pls.origin);
237 	}
238 
239 	return len;
240 }
241 
242 // Is player visible?
243 static qboolean
Cam_IsVisible(player_state_t * player,vec3_t vec)244 Cam_IsVisible (player_state_t * player, vec3_t vec)
245 {
246 	float       d;
247 	trace_t     trace;
248 	vec3_t      v;
249 
250 	trace = Cam_DoTrace (player->pls.origin, vec);
251 	if (trace.fraction != 1 || /* trace.inopen || */ trace.inwater)
252 		return false;
253 	// check distance, don't let the player get too far away or too close
254 	VectorSubtract (player->pls.origin, vec, v);
255 	d = VectorLength (v);
256 
257 	return (d > 16.0);
258 }
259 
260 static qboolean
InitFlyby(player_state_t * self,player_state_t * player,int checkvis)261 InitFlyby (player_state_t * self, player_state_t * player, int checkvis)
262 {
263 	float       f, max;
264 	vec3_t      forward, right, up, vec, vec2;
265 
266 	VectorCopy (player->viewangles, vec);
267 	vec[0] = 0;
268 	AngleVectors (vec, forward, right, up);
269 //	for (i = 0; i < 3; i++)
270 //		forward[i] *= 3;
271 
272 	max = 1000;
273 	VectorAdd (forward, up, vec2);
274 	VectorAdd (vec2, right, vec2);
275 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
276 		max = f;
277 		VectorCopy (vec2, vec);
278 	}
279 	VectorAdd (forward, up, vec2);
280 	VectorSubtract (vec2, right, vec2);
281 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
282 		max = f;
283 		VectorCopy (vec2, vec);
284 	}
285 	VectorAdd (forward, right, vec2);
286 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
287 		max = f;
288 		VectorCopy (vec2, vec);
289 	}
290 	VectorSubtract (forward, right, vec2);
291 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
292 		max = f;
293 		VectorCopy (vec2, vec);
294 	}
295 	VectorAdd (forward, up, vec2);
296 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
297 		max = f;
298 		VectorCopy (vec2, vec);
299 	}
300 	VectorSubtract (forward, up, vec2);
301 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
302 		max = f;
303 		VectorCopy (vec2, vec);
304 	}
305 	VectorAdd (up, right, vec2);
306 	VectorSubtract (vec2, forward, vec2);
307 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
308 		max = f;
309 		VectorCopy (vec2, vec);
310 	}
311 	VectorSubtract (up, right, vec2);
312 	VectorSubtract (vec2, forward, vec2);
313 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
314 		max = f;
315 		VectorCopy (vec2, vec);
316 	}
317 	// invert
318 	VectorNegate (forward, vec2);
319 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
320 		max = f;
321 		VectorCopy (vec2, vec);
322 	}
323 	VectorCopy (forward, vec2);
324 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
325 		max = f;
326 		VectorCopy (vec2, vec);
327 	}
328 	// invert
329 	VectorNegate (right, vec2);
330 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
331 		max = f;
332 		VectorCopy (vec2, vec);
333 	}
334 	VectorCopy (right, vec2);
335 	if ((f = Cam_TryFlyby (self, player, vec2, checkvis)) < max) {
336 		max = f;
337 		VectorCopy (vec2, vec);
338 	}
339 	// ack, can't find him
340 	if (max >= 1000) {
341 //		Cam_Unlock ();
342 		return false;
343 	}
344 	locked = true;
345 	VectorCopy (vec, desired_position);
346 	return true;
347 }
348 
349 static void
Cam_CheckHighTarget(void)350 Cam_CheckHighTarget (void)
351 {
352 	int				i, j, max;
353 	player_info_t  *s;
354 
355 	j = -1;
356 	for (i = 0, max = -9999; i < MAX_CLIENTS; i++) {
357 		s = &cl.players[i];
358 		if (s->name && s->name->value[0] && !s->spectator && s->frags > max) {
359 			max = s->frags;
360 			j = i;
361 		}
362 	}
363 	if (j >= 0) {
364 		if (!locked || cl.players[j].frags > cl.players[spec_track].frags) {
365 			Cam_Lock (j);
366 			ideal_track = spec_track;
367 		}
368 	} else
369 		Cam_Unlock ();
370 }
371 
372 // ZOID
373 //
374 // Take over the user controls and track a player.
375 // We find a nice position to watch the player and move there
376 void
Cam_Track(usercmd_t * cmd)377 Cam_Track (usercmd_t *cmd)
378 {
379 	float			len;
380 	frame_t		   *frame;
381 	player_state_t *player, *self;
382 	vec3_t			vec;
383 
384 	if (!cl.spectator)
385 		return;
386 
387 	if (cl_hightrack->int_val && !locked)
388 		Cam_CheckHighTarget ();
389 
390 	if (!autocam || cls.state != ca_active)
391 		return;
392 
393 	if (locked
394 		&& (!cl.players[spec_track].name->value[0]
395 			|| cl.players[spec_track].spectator)) {
396 		locked = false;
397 		if (cl_hightrack->int_val)
398 			Cam_CheckHighTarget ();
399 		else
400 			Cam_Unlock ();
401 		return;
402 	}
403 
404 	frame = &cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK];
405 	if (autocam && cls.demoplayback2 && 0) {
406 		if (ideal_track != spec_track && realtime - last_lock > 1
407 			&& frame->playerstate[ideal_track].messagenum == cl.parsecount)
408 			Cam_Lock (ideal_track);
409 
410 		if (frame->playerstate[spec_track].messagenum != cl.parsecount) {
411 			int         i;
412 
413 			for (i = 0; i < MAX_CLIENTS; i++) {
414 				if (frame->playerstate[i].messagenum == cl.parsecount)
415 					break;
416 			}
417 			if (i < MAX_CLIENTS)
418 				Cam_Lock (i);
419 		}
420 	}
421 
422 	player = frame->playerstate + spec_track;
423 	self = frame->playerstate + cl.playernum;
424 
425 	if (!locked || !Cam_IsVisible (player, desired_position)) {
426 		if (!locked || realtime - cam_lastviewtime > 0.1) {
427 			if (!InitFlyby (self, player, true))
428 				InitFlyby (self, player, false);
429 			cam_lastviewtime = realtime;
430 		}
431 	} else
432 		cam_lastviewtime = realtime;
433 
434 	// couldn't track for some reason
435 	if (!locked || !autocam)
436 		return;
437 
438 	if (cl_chasecam->int_val) {
439 		cmd->forwardmove = cmd->sidemove = cmd->upmove = 0;
440 
441 		VectorCopy (player->viewangles, cl.viewangles);
442 		VectorCopy (player->pls.origin, desired_position);
443 		if (memcmp (&desired_position, &self->pls.origin,
444 					sizeof (desired_position)) != 0) {
445 			if (!cls.demoplayback) {
446 				MSG_WriteByte (&cls.netchan.message, clc_tmove);
447 				MSG_WriteCoordV (&cls.netchan.message, desired_position);
448 			}
449 			// move there locally immediately
450 			VectorCopy (desired_position, self->pls.origin);
451 		}
452 		self->pls.weaponframe = player->pls.weaponframe;
453 
454 	} else {
455 		// Ok, move to our desired position and set our angles to view
456 		// the player
457 		VectorSubtract (desired_position, self->pls.origin, vec);
458 		len = VectorLength (vec);
459 		cmd->forwardmove = cmd->sidemove = cmd->upmove = 0;
460 		if (len > 16) {					// close enough?
461 			if (!cls.demoplayback) {
462 				MSG_WriteByte (&cls.netchan.message, clc_tmove);
463 				MSG_WriteCoordV (&cls.netchan.message, desired_position);
464 			}
465 		}
466 		// move there locally immediately
467 		VectorCopy (desired_position, self->pls.origin);
468 
469 		VectorSubtract (player->pls.origin, desired_position, vec);
470 		vectoangles (vec, cl.viewangles);
471 		cl.viewangles[0] = -cl.viewangles[0];
472 	}
473 }
474 
475 #if 0
476 static float
477 adjustang (float current, float ideal, float speed)
478 {
479 	float		move;
480 
481 	current = anglemod (current);
482 	ideal = anglemod (ideal);
483 
484 	if (current == ideal)
485 		return current;
486 
487 	move = ideal - current;
488 	if (ideal > current) {
489 		if (move >= 180)
490 			move = move - 360;
491 	} else {
492 		if (move <= -180)
493 			move = move + 360;
494 	}
495 	if (move > 0) {
496 		if (move > speed)
497 			move = speed;
498 	} else {
499 		if (move < -speed)
500 			move = -speed;
501 	}
502 
503 	return anglemod (current + move);
504 }
505 #endif
506 
507 #if 0
508 void
509 Cam_SetView (void)
510 {
511 	frame_t		   *frame;
512 	player_state_t *player, *self;
513 	vec3_t			vec, vec2;
514 
515 	if (cls.state != ca_active || !cl.spectator || !autocam || !locked)
516 		return;
517 
518 	frame = &cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK];
519 	player = frame->playerstate + spec_track;
520 	self = frame->playerstate + cl.playernum;
521 
522 	VectorSubtract (player->pls.origin, cl.simorg, vec);
523 	if (cam_forceview) {
524 		cam_forceview = false;
525 		vectoangles (vec, cam_viewangles);
526 		cam_viewangles[0] = -cam_viewangles[0];
527 	} else {
528 		vectoangles (vec, vec2);
529 		vec2[PITCH] = -vec2[PITCH];
530 
531 		cam_viewangles[PITCH] =
532 			adjustang (cam_viewangles[PITCH], vec2[PITCH],
533 					   cl_camera_maxpitch->value);
534 		cam_viewangles[YAW] =
535 			adjustang (cam_viewangles[YAW], vec2[YAW],
536 					   cl_camera_maxyaw->value);
537 	}
538 	VectorCopy (cam_viewangles, cl.viewangles);
539 	VectorCopy (cl.viewangles, cl.simangles);
540 	cl.simangles[ROLL] = 0;						// FIXME @@@
541 }
542 #endif
543 
544 void
Cam_FinishMove(usercmd_t * cmd)545 Cam_FinishMove (usercmd_t *cmd)
546 {
547 	int				end, i;
548 	player_info_t  *s;
549 
550 	if (cls.state != ca_active)
551 		return;
552 
553 	if (!cl.spectator)					// only in spectator mode
554 		return;
555 
556 #if 0
557 	if (autocam && locked) {
558 		frame = &cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK];
559 		player = frame->playerstate + spec_track;
560 		self = frame->playerstate + cl.playernum;
561 
562 		VectorSubtract (player->pls.origin, self->pls.origin, vec);
563 		if (cam_forceview) {
564 			cam_forceview = false;
565 			vectoangles (vec, cam_viewangles);
566 			cam_viewangles[0] = -cam_viewangles[0];
567 		} else {
568 			vectoangles (vec, vec2);
569 			vec2[PITCH] = -vec2[PITCH];
570 
571 			cam_viewangles[PITCH] =
572 				adjustang (cam_viewangles[PITCH], vec2[PITCH],
573 						   cl_camera_maxpitch->value);
574 			cam_viewangles[YAW] =
575 				adjustang (cam_viewangles[YAW], vec2[YAW],
576 						   cl_camera_maxyaw->value);
577 		}
578 		VectorCopy (cam_viewangles, cl.viewangles);
579 	}
580 #endif
581 
582 	if (cmd->buttons & BUTTON_ATTACK) {
583 		if (!(oldbuttons & BUTTON_ATTACK)) {
584 			oldbuttons |= BUTTON_ATTACK;
585 			autocam++;
586 
587 			if (autocam > CAM_TRACK) {
588 				Cam_Unlock ();
589 				VectorCopy (cl.viewangles, cmd->angles);
590 				return;
591 			}
592 		} else
593 			return;
594 	} else {
595 		oldbuttons &= ~BUTTON_ATTACK;
596 		if (!autocam)
597 			return;
598 	}
599 
600 	if (autocam && cl_hightrack->int_val) {
601 		Cam_CheckHighTarget ();
602 		return;
603 	}
604 
605 	if (locked) {
606 		if ((cmd->buttons & BUTTON_JUMP) && (oldbuttons & BUTTON_JUMP))
607 			return;						// don't pogo stick
608 
609 		if (!(cmd->buttons & BUTTON_JUMP)) {
610 			oldbuttons &= ~BUTTON_JUMP;
611 			return;
612 		}
613 		oldbuttons |= BUTTON_JUMP;		// don't jump again until released
614 	}
615 //	Sys_Printf ("Selecting track target...\n");
616 
617 	if (locked && autocam)
618 		end = (spec_track + 1) % MAX_CLIENTS;
619 	else
620 		end = spec_track;
621 	i = end;
622 	do {
623 		s = &cl.players[i];
624 		if (s->name && s->name->value[0] && !s->spectator) {
625 			Cam_Lock (i);
626 			ideal_track = i;
627 			return;
628 		}
629 		i = (i + 1) % MAX_CLIENTS;
630 	} while (i != end);
631 	// stay on same guy?
632 	i = spec_track;
633 	s = &cl.players[i];
634 	if (s->name && s->name->value[0] && !s->spectator) {
635 		Cam_Lock (i);
636 		ideal_track = i;
637 		return;
638 	}
639 	Sys_Printf ("No target found ...\n");
640 	autocam = locked = false;
641 }
642 
643 void
Cam_Reset(void)644 Cam_Reset (void)
645 {
646 	autocam = CAM_NONE;
647 	spec_track = 0;
648 	ideal_track = 0;
649 }
650 
651 void
CL_Cam_Init_Cvars(void)652 CL_Cam_Init_Cvars (void)
653 {
654 	cl_camera_maxpitch = Cvar_Get ("cl_camera_maxpitch", "10", CVAR_NONE, NULL,
655 								   "highest camera pitch in spectator mode");
656 	cl_camera_maxyaw = Cvar_Get ("cl_camera_maxyaw", "30", CVAR_NONE, NULL,
657 								 "highest camera yaw in spectator mode");
658 	cl_chasecam = Cvar_Get ("cl_chasecam", "0", CVAR_NONE, NULL, "get first "
659 							"person view of the person you are tracking in "
660 							"spectator mode");
661 	cl_hightrack = Cvar_Get ("cl_hightrack", "0", CVAR_NONE, NULL, "view the "
662 							 "player who has the most frags while you are in "
663 							 "spectator mode.");
664 }
665