1/*
2Copyright (C) 1996-1997 Id Software, Inc.
3
4This program is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6as published by the Free Software Foundation; either version 2
7of the License, or (at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13See the included (GNU.txt) GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, 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
38static vec3_t desired_position; // where the camera wants to be
39static qboolean locked = false;
40static int oldbuttons;
41
42// track high fragger
43cvar_t cl_hightrack = CVAR2("cl_hightrack", "0" );
44
45cvar_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
50qboolean cam_forceview;
51vec3_t cam_viewangles;
52double cam_lastviewtime;
53
54int spec_track = 0; // player# of who we are tracking
55int autocam = CAM_NONE;
56
57static 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
87static 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
93qboolean 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
104qboolean 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
112void 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
123void 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
136pmtrace_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
151static 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?
184static 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
201static 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
291static 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
315void 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
395static 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
433void 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
465void 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
560void Cam_Reset(void)
561{
562	autocam = CAM_NONE;
563	spec_track = 0;
564}
565
566void 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