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
vectoangles(vec3_t vec,vec3_t ang)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
vlen(vec3_t v)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
Cam_DrawViewModel(void)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
Cam_DrawPlayer(int playernum)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
Cam_Unlock(void)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
Cam_Lock(int playernum)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
Cam_DoTrace(vec3_t vec1,vec3_t vec2)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
Cam_TryFlyby(player_state_t * self,player_state_t * player,vec3_t vec,qboolean checkvis)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?
Cam_IsVisible(player_state_t * player,vec3_t vec)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
InitFlyby(player_state_t * self,player_state_t * player,int checkvis)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
Cam_CheckHighTarget(void)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
Cam_Track(usercmd_t * cmd)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
Cam_FinishMove(usercmd_t * cmd)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
Cam_Reset(void)560 void Cam_Reset(void)
561 {
562 autocam = CAM_NONE;
563 spec_track = 0;
564 }
565
CL_InitCam(void)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