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