//----------------------------------------------------------------------------- // // $Id$ // //----------------------------------------------------------------------------- // // $Log$ // Revision 1.23 2005/09/07 11:03:31 jbravo // Minor fix // // Revision 1.22 2003/04/09 02:00:43 jbravo // Fixed team none in DM and some final cleanups for the 3.0 release // // Revision 1.21 2003/04/02 22:23:51 jbravo // More replacements tweaks. Added zcam_stfu // // Revision 1.20 2002/08/18 15:10:32 makro // Temporarily disabled the call to specPrint // // Revision 1.19 2002/08/13 16:59:16 makro // Fixed per-client callvote limit; added a new cvar - g_RQ3_maxClientVotes // // Revision 1.18 2002/08/03 19:27:37 jbravo // Made a booboo in my tracking message code // // Revision 1.17 2002/08/03 18:24:13 jbravo // Tweaked my zcam swing change a bit. // // Revision 1.16 2002/08/03 07:49:49 jbravo // Added a line to the zcam swing tracking message indicating who you are following // // Revision 1.15 2002/07/04 04:20:41 jbravo // Fixed my weaponchange cancel in the Use cmd, and fixed the bug where players // that where in eye spectating someone moved on to another player instantly on death. // // Revision 1.14 2002/06/16 20:06:14 jbravo // Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap" // // Revision 1.13 2002/05/09 20:58:30 jbravo // New Obit system and a warning cleanup in zcam // // Revision 1.12 2002/05/06 00:35:49 jbravo // Small fixes to misc stuff // // Revision 1.11 2002/05/01 21:14:59 jbravo // Misc fixes // // Revision 1.10 2002/03/31 03:31:24 jbravo // Compiler warning cleanups // // Revision 1.9 2002/03/30 19:33:29 jbravo // Made CameraFlicBegin and CameraSwingCycle non static for M$ VC // // Revision 1.8 2002/03/30 02:29:43 jbravo // Lots of spectator code updates. Removed debugshit, added some color. // // Revision 1.7 2002/03/03 02:04:15 jbravo // Zcam tewaking // // Revision 1.6 2002/02/10 18:38:42 jbravo // Added new SPECTATOR_ZCAM spec mode. // // Revision 1.5 2002/02/10 16:26:55 jbravo // Attempting to intergrate zcam better into rq3 and a fix for lights.wav // // Revision 1.4 2002/02/09 01:06:36 jbravo // Added zcam files // // //----------------------------------------------------------------------------- /* * ZCam v1.0.4 * Spectator Camera for Quake III Arena * Copyright (C), 2001 by Avi "Zung!" Rozen * * http://www.telefragged.com/zungbang/zcam * * Credits: * FLIC camera mode is based on code taken from q2cam by Paul Jordan * SWING camera mode is based on ideas taken from CreepCam for Quake I * */ #include "g_local.h" #include "zcam.h" /* external function prototypes */ qboolean IsVisible(gentity_t * player1, gentity_t * player2, float maxrange); int NumPlayersVisible(gentity_t * viewer, float maxrange); gentity_t *PriorityTarget(gentity_t * target, qboolean * override); gentity_t *PlayerToFollow(gentity_t * ent, qboolean * override); gentity_t *PlayerToTrack(gentity_t * ent, gentity_t * target1st); /* constants */ #define DAMP_ANGLE_Y 10 #define DAMP_VALUE_XY 6 #define DAMP_VALUE_Z 3 #define CAMERA_MIN_RANGE 48 #define CAMERA_MAX_RANGE 800 #define CAMERA_SWITCH_TIME 20000 #define CAMERA_DEAD_SWITCH_TIME 2000 #define CAMERA_MIN_SWITCH_TIME 4000 #define SWING_ANGLE_STEP 10.0F #define SWING_DISTANCE_STEP 5.0F #define SWING_VIEWANGLE_STEP 6.0F #define SWING_NOMINAL_DISTANCE 80.0F #define SWING_NOMINAL_HEIGHT 0.0F #define SWING_FOV_FACTOR 1.25F #define SWING_MSG_TIME 1000 #ifndef min #define min(a, b) ((a) < (b) ? (a) : (b)) #endif #ifndef Q3_VM #ifndef acosf #define acosf(x) ((float)acos(x)) #endif #ifndef asinf #define asinf(x) ((float)asin(x)) #endif #else #define sqrtf(x) ((float)sqrt(x)) /* math routines */ #define PI 3.14159265358979323846264338327950288419716939937510f static float asin_consts[4] = { 5.0505363E-02f, 3.9721134E-02f, 7.5464795E-02f, 1.6665163E-01f }; static int errno; static float _polyf(float g) { int i; float *p = asin_consts; float Result; if (g == 0.0f) return (0.0f); Result = g * (*p++); for (i = 3; i > 0; i--) Result = g * (Result + (*p++)); return Result; } static float acosf(float x) { float g, y, Result; y = (x < 0.0f) ? (-x) : x; if (y > 0.5f) { if (y > 1.0f) { errno = 1; y = 1.0f; } g = (1.0f - y) * 0.5f; y = (-2.0f) * sqrtf(g); Result = _polyf(g); Result = y * (1 + Result); if (x <= 0.0f) Result = PI + Result; else Result = -Result; } else { g = y * y; Result = _polyf(g); Result = y * (1 + Result); if (x <= 0.0f) Result = (PI * 0.5f) + Result; else Result = (PI * 0.5f) - Result; } return Result; } static float asinf(float x) { float g, y, Result; y = (x < 0.0f) ? (-x) : x; if (y > 0.5f) { if (y > 1.0f) { errno = 1; y = 1.0f; } g = (1.0f - y) * 0.5f; y = (-2.0f) * sqrtf(g); Result = _polyf(g); Result = PI * 0.5f + y * (1 + Result); } else { g = y * y; Result = _polyf(g); Result = y * (1 + Result); } if (x < 0.0f) Result = -Result; return Result; } #endif /* Q3_VM */ /* local data */ static camera_t cameras[MAX_CLIENTS]; /* * ============= * TempVector * * This is just a convenience function * for making temporary vectors for function calls * ============= * */ float *tv(float x, float y, float z) { static int index; static vec3_t vecs[8]; float *v; // use an array so that multiple tempvectors won't collide // for a while v = vecs[index]; index = (index + 1) & 7; v[0] = x; v[1] = y; v[2] = z; return v; } /* local functions */ static gentity_t *ClosestVisible(gentity_t * ent, float maxrange, qboolean pvs) { int i; gentity_t *target; gentity_t *best = NULL; vec3_t distance; float current, closest = -1.0F; for (i = 0; i < level.maxclients; i++) { target = &g_entities[i]; if (target != ent && level.clients[i].pers.connected == CON_CONNECTED && level.clients[i].sess.sessionTeam != TEAM_SPECTATOR && target->client->ps.pm_type == PM_NORMAL && ((pvs) ? trap_InPVS(ent->client->ps.origin, target->client->ps.origin) : IsVisible(ent, target, maxrange))) { VectorSubtract(target->client->ps.origin, ent->client->ps.origin, distance); current = VectorLength(distance); if (closest < 0 || current < closest) { best = target; closest = current; } } } return best; } // count all players, excluding spectators static int NumPlayers(void) { int i, count = 0; for (i = 0; i < level.maxclients; i++) { if (level.clients[i].pers.connected == CON_CONNECTED && level.clients[i].sess.sessionTeam != TEAM_SPECTATOR) { count++; } } return count; } static void PointCamAtOrigin(gentity_t * ent, vec3_t location) { vec3_t diff, angles; VectorSubtract(location, ent->client->ps.origin, diff); vectoangles(diff, angles); SetClientViewAngle(ent, angles); } static void PointCamAtTarget(gentity_t * ent) { vec3_t diff, angles; float difference; if (ent == NULL || ent->client->camera->flic_target == NULL || (ent->client->camera->flic_watching_the_wall && !IsVisible(ent, ent->client->camera->flic_target, 0))) return; VectorSubtract(ent->client->camera->flic_target->client->ps.origin, ent->client->ps.origin, diff); vectoangles(diff, angles); angles[2] = 0; difference = angles[1] - ent->s.angles[1]; while (fabs(difference) > 180) { if (difference > 0) { difference -= 360; } else { difference += 360; } } if (fabs(difference) > ent->client->camera->flic_angle_lag) { // upto twice the angular velocity when |difference| > 20 deg if (difference > 0) { angles[1] += (difference < 20) ? ent->client->camera->flic_angle_lag : ((1 + (difference - 20) / 160) * ent->client->camera->flic_angle_lag); } else { angles[1] -= (difference > -20) ? ent->client->camera->flic_angle_lag : ((1 + (-difference - 20) / 160) * ent->client->camera->flic_angle_lag); } } SetClientViewAngle(ent, angles); } static qboolean InSolid(gentity_t * ent) { int contents; contents = trap_PointContents(ent->client->ps.origin, ent->s.clientNum); return ((contents & CONTENTS_SOLID) != 0); } static void FindCamPos(gentity_t * ent, float angle, vec3_t offset_position, vec3_t cam_pos) { vec3_t forward; AngleVectors(tv(ent->client->camera->flic_target->client->ps.viewangles[PITCH], ent->client->camera->flic_target->client->ps.viewangles[YAW] + angle, ent->client->camera->flic_target->client->ps.viewangles[ROLL]), forward, NULL, NULL); forward[2] = 0; VectorNormalize(forward); cam_pos[0] = ent->client->camera->flic_target->client->ps.origin[0] + (offset_position[0] * forward[0]); cam_pos[1] = ent->client->camera->flic_target->client->ps.origin[1] + (offset_position[1] * forward[1]); cam_pos[2] = ent->client->camera->flic_target->client->ps.origin[2] + offset_position[2]; } static void RepositionAtTarget(gentity_t * ent, vec3_t offset_position) { vec3_t diff; vec3_t cam_pos; trace_t trace; qboolean snapto = qfalse; // snapto towards target when jumping to new position ent->client->camera->flic_watching_the_wall = qfalse; // try to be behind target, but if too close // try to be on his right/left/front FindCamPos(ent, 0, offset_position, cam_pos); trap_Trace(&trace, ent->client->camera->flic_target->client->ps.origin, NULL, NULL, cam_pos, ent->client->camera->flic_target->s.clientNum, CONTENTS_SOLID); if (trace.fraction < 1) { VectorSubtract(trace.endpos, ent->client->camera->flic_target->client->ps.origin, diff); if (VectorLength(diff) < 40) { FindCamPos(ent, 90, offset_position, cam_pos); trap_Trace(&trace, ent->client->camera->flic_target->client->ps.origin, NULL, NULL, cam_pos, ent->client->camera->flic_target->s.clientNum, CONTENTS_SOLID); if (trace.fraction < 1) { VectorSubtract(trace.endpos, ent->client->camera->flic_target->client->ps.origin, diff); if (VectorLength(diff) < 40) { FindCamPos(ent, -90, offset_position, cam_pos); trap_Trace(&trace, ent->client->camera->flic_target->client->ps.origin, NULL, NULL, cam_pos, ent->client->camera->flic_target->s.clientNum, CONTENTS_SOLID); if (trace.fraction < 1) { VectorSubtract(trace.endpos, ent->client->camera->flic_target->client->ps.origin, diff); if (VectorLength(diff) < 40) { FindCamPos(ent, 180, offset_position, cam_pos); trap_Trace(&trace, ent->client->camera->flic_target->client->ps.origin, NULL, NULL, cam_pos, ent->client->camera->flic_target->s.clientNum, CONTENTS_SOLID); } } } } } VectorNormalize(diff); VectorMA(trace.endpos, -8, diff, trace.endpos); if (trace.plane.normal[2] > 0.8) trace.endpos[2] += 4; snapto = qtrue; } if (fabs(trace.endpos[0] - ent->client->ps.origin[0]) > ent->client->camera->flic_xy_lag) if (trace.endpos[0] > ent->client->ps.origin[0]) ent->client->ps.origin[0] += ent->client->camera->flic_xy_lag; else ent->client->ps.origin[0] -= ent->client->camera->flic_xy_lag; else ent->client->ps.origin[0] = trace.endpos[0]; if (fabs(trace.endpos[1] - ent->client->ps.origin[1]) > ent->client->camera->flic_xy_lag) if (trace.endpos[1] > ent->client->ps.origin[1]) ent->client->ps.origin[1] += ent->client->camera->flic_xy_lag; else ent->client->ps.origin[1] -= ent->client->camera->flic_xy_lag; else ent->client->ps.origin[1] = trace.endpos[1]; if (fabs(trace.endpos[2] - ent->client->ps.origin[2]) > ent->client->camera->flic_z_lag) if (trace.endpos[2] > ent->client->ps.origin[2]) ent->client->ps.origin[2] += ent->client->camera->flic_z_lag; else ent->client->ps.origin[2] -= ent->client->camera->flic_z_lag; else ent->client->ps.origin[2] = trace.endpos[2]; trap_Trace(&trace, ent->client->camera->flic_target->client->ps.origin, NULL, NULL, ent->client->ps.origin, ent->client->camera->flic_target->s.clientNum, CONTENTS_SOLID); if (trace.fraction < 1) { VectorSubtract(trace.endpos, ent->client->camera->flic_target->client->ps.origin, diff); VectorNormalize(diff); VectorMA(trace.endpos, -8, diff, trace.endpos); if (trace.plane.normal[2] > 0.8) trace.endpos[2] += 4; VectorCopy(trace.endpos, ent->client->ps.origin); snapto = qtrue; } if (snapto) { vec3_t angles; VectorSubtract(ent->client->camera->flic_target->client->ps.origin, ent->client->ps.origin, diff); vectoangles(diff, angles); SetClientViewAngle(ent, angles); } } static void RepositionAtOrigin(gentity_t * ent, vec3_t offset_position) { vec3_t cam_pos; trace_t trace; cam_pos[0] = offset_position[0] + 40; cam_pos[1] = offset_position[1] + 40; cam_pos[2] = offset_position[2] + 30; trap_Trace(&trace, offset_position, NULL, NULL, cam_pos, ent->client->camera->flic_target->s.clientNum, CONTENTS_SOLID); if (trace.fraction < 1) { vec3_t diff; VectorSubtract(trace.endpos, offset_position, diff); VectorNormalize(diff); VectorMA(trace.endpos, -8, diff, trace.endpos); if (trace.plane.normal[2] > 0.8) trace.endpos[2] += 4; } if (fabs(trace.endpos[0] - ent->client->ps.origin[0]) > ent->client->camera->flic_xy_lag) { if (trace.endpos[0] > ent->client->ps.origin[0]) { ent->client->ps.origin[0] += ent->client->camera->flic_xy_lag; } else { ent->client->ps.origin[0] -= ent->client->camera->flic_xy_lag; } } else { ent->client->ps.origin[0] = trace.endpos[0]; } if (fabs(trace.endpos[1] - ent->client->ps.origin[1]) > ent->client->camera->flic_xy_lag) { if (trace.endpos[1] > ent->client->ps.origin[1]) { ent->client->ps.origin[1] += ent->client->camera->flic_xy_lag; } else { ent->client->ps.origin[1] -= ent->client->camera->flic_xy_lag; } } else { ent->client->ps.origin[1] = trace.endpos[1]; } if (fabs(trace.endpos[2] - ent->client->ps.origin[2]) > ent->client->camera->flic_z_lag) { if (trace.endpos[2] > ent->client->ps.origin[2]) { ent->client->ps.origin[2] += ent->client->camera->flic_z_lag; } else { ent->client->ps.origin[2] -= ent->client->camera->flic_z_lag; } } else { ent->client->ps.origin[2] = trace.endpos[2]; } trap_Trace(&trace, offset_position, NULL, NULL, ent->client->ps.origin, ent->client->camera->flic_target->s.clientNum, CONTENTS_SOLID); if (trace.fraction < 1) { vec3_t diff; VectorSubtract(trace.endpos, offset_position, diff); VectorNormalize(diff); VectorMA(trace.endpos, -8, diff, trace.endpos); if (trace.plane.normal[2] > 0.8) trace.endpos[2] += 4; VectorCopy(trace.endpos, ent->client->ps.origin); } } static void SwitchToNewTarget(gentity_t * ent, gentity_t * new_target) { if (ent->client->camera->flic_target == NULL) { ent->client->camera->flic_target = new_target; ent->client->camera->flic_last_switch_time = level.time + CAMERA_MIN_SWITCH_TIME; } else if (ent->client->camera->flic_target != new_target) { if (ent->client->camera->flic_last_switch_time < level.time) { ent->client->camera->flic_target = new_target; ent->client->camera->flic_last_switch_time = level.time + CAMERA_MIN_SWITCH_TIME; } } if (ent->client->camera->flic_target == NULL) ent->client->camera->flic_last_switch_time = 0; } static void CameraFlicThink(gentity_t * ent) { vec3_t camera_offset; int num_visible; gentity_t *new_target; // move towards target if inside solid if (ent->client->camera->flic_target && !ent->client->camera->flic_watching_the_dead && InSolid(ent)) { RepositionAtTarget(ent, tv(-60, -60, 40)); } num_visible = NumPlayersVisible(ent, MAX_VISIBLE_RANGE); new_target = PlayerToFollow(ent, &ent->client->camera->flic_override); // only watch the dead if it's the one we followed if (!ent->client->camera->flic_watching_the_dead && ent->client->camera->flic_target && ent->client->camera->flic_target->client->ps.pm_type == PM_DEAD) { ent->client->camera->flic_watching_the_dead = qtrue; ent->client->camera->flic_last_move_time = level.time + CAMERA_DEAD_SWITCH_TIME; PointCamAtTarget(ent); } else if (ent->client->camera->flic_watching_the_dead) { if (ent->client->camera->flic_last_move_time < level.time || InSolid(ent)) { ent->client->camera->flic_watching_the_dead = qfalse; } else { if (ent->client->camera->flic_target->client->ps.pm_type == PM_DEAD) { VectorCopy(ent->client->camera->flic_target->client->ps.origin, ent->client->camera->flic_dead_origin); } PointCamAtOrigin(ent, ent->client->camera->flic_dead_origin); RepositionAtOrigin(ent, ent->client->camera->flic_dead_origin); } } else if (num_visible < 2) { camera_offset[0] = -60; camera_offset[1] = -60; camera_offset[2] = 40; if (ent->client->camera->flic_last_move_time >= level.time) { gentity_t *closest_target; if (new_target != NULL && (ent->client->camera->flic_override || NumPlayersVisible(new_target, MAX_VISIBLE_RANGE) > 1)) { SwitchToNewTarget(ent, new_target); RepositionAtTarget(ent, camera_offset); PointCamAtTarget(ent); } else if ((closest_target = ClosestVisible(ent, MAX_VISIBLE_RANGE, qfalse)) != NULL) { SwitchToNewTarget(ent, closest_target); RepositionAtTarget(ent, camera_offset); PointCamAtTarget(ent); } else if (new_target != NULL) { // look for someone new! SwitchToNewTarget(ent, new_target); RepositionAtTarget(ent, camera_offset); PointCamAtTarget(ent); ent->client->camera->flic_last_move_time = 0; } } else if (new_target != NULL) { // just keep looking for action! camera_offset[0] = -60; camera_offset[1] = -60; camera_offset[2] = 40; SwitchToNewTarget(ent, new_target); RepositionAtTarget(ent, camera_offset); PointCamAtTarget(ent); } } // if we are done during a battle. else if (ent->client->camera->flic_last_move_time < level.time || (ent->client->camera->flic_target && !trap_InPVS(ent->client->ps.origin, ent->client->camera->flic_target->client->ps.origin)) || (ent->client->camera->flic_target && InSolid(ent))) { if (new_target != NULL) { camera_offset[0] = -60; camera_offset[1] = -60; camera_offset[2] = 80; ent->client->camera->flic_target = NULL; SwitchToNewTarget(ent, new_target); RepositionAtTarget(ent, camera_offset); PointCamAtTarget(ent); ent->client->camera->flic_last_move_time = level.time + CAMERA_SWITCH_TIME; } } else if (ent->client->camera->flic_target != NULL) { if (IsVisible(ent, ent->client->camera->flic_target, 0)) { float distance; vec3_t diff; VectorSubtract(ent->client->ps.origin, ent->client->camera->flic_target->client->ps.origin, diff); distance = VectorLength(diff); if (distance < CAMERA_MIN_RANGE || distance > CAMERA_MAX_RANGE) RepositionAtTarget(ent, tv(-60, -60, 80)); PointCamAtTarget(ent); } else ent->client->camera->flic_last_move_time = 0; } } void CameraFlicBegin(gentity_t * ent) { int clientNum = ent - g_entities; cameras[clientNum].mode = CAMERA_MODE_FLIC; cameras[clientNum].flic_target = NULL; cameras[clientNum].swing_target = NULL; camera_begin(ent); // JBravo: no need for this extra spam // CameraShowMode (ent); } static void CameraStaticThink(gentity_t * ent) { trace_t trace; vec3_t end_floor, end_ceiling; static vec3_t mins = { -4, -4, -4 }; static vec3_t maxs = { 4, 4, 4 }; end_floor[0] = ent->client->ps.origin[0]; end_floor[1] = ent->client->ps.origin[1]; end_floor[2] = ent->client->ps.origin[2] - 40000; trap_Trace(&trace, ent->client->ps.origin, mins, maxs, end_floor, ent->s.clientNum, CONTENTS_SOLID); VectorCopy(trace.endpos, end_floor); end_ceiling[0] = end_floor[0]; end_ceiling[1] = end_floor[1]; end_ceiling[2] = end_floor[2] + 175; trap_Trace(&trace, end_floor, mins, maxs, end_ceiling, ent->s.clientNum, CONTENTS_SOLID); VectorCopy(trace.endpos, ent->client->ps.origin); if (ent->client->camera->flic_last_move_time < level.time) { vec3_t angles; ent->client->camera->flic_last_move_time = level.time + 2000; angles[0] = 45; angles[1] = 0; angles[2] = 0; SetClientViewAngle(ent, angles); } } /* limit angle to [-180, 180] */ static float AngleMod180(float angle) { while (fabs(angle) > 180) if (angle > 0) angle -= 360; else angle += 360; return angle; } /* subtract angle b from a to give minimum difference assume a and b are in [-180, 180] */ static float AngleDiff(float a, float b) { float c, c1, c2, c3; c1 = a - b; c2 = a - (b + 360); c3 = a - (b - 360); c = c1; if (fabs(c2) < fabs(c)) c = c2; if (fabs(c3) < fabs(c)) c = c3; return c; } static float AngleClamp(float angle, float limit) { if (angle > limit) return limit; if (angle < -limit) return -limit; return angle; } static gentity_t *CameraSwingTarget(gentity_t * ent) { gentity_t *target1st, *target2nd; target1st = ent->client->camera->swing_target; target2nd = (target1st != NULL) ? PlayerToTrack(ent, target1st) : NULL; if (target2nd != ent->client->camera->swing_secondry_target && target2nd != NULL && ent->client->camera->swing_msg_time <= level.time) { char *color; ent->client->camera->swing_msg_time = 0; if (target2nd->client->sess.sessionTeam == TEAM_RED) color = S_COLOR_RED; else if (target2nd->client->sess.sessionTeam == TEAM_BLUE) color = S_COLOR_BLUE; else if (target1st->client->sess.sessionTeam != target2nd->client->sess.sessionTeam) color = S_COLOR_RED; else color = S_COLOR_GREEN; // JBravo: take teamnames into account // JBravo: added who you are following to the zcam swing output. // JBravo: that is if the client wants to see it if (!ent->client->zcam_stfu) { trap_SendServerCommand(ent->client->ps.clientNum, va("cp \"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n%sFollowing " S_COLOR_WHITE "%s" S_COLOR_RED "/" S_COLOR_MAGENTA "%s\n%sTracking " S_COLOR_WHITE "%s" S_COLOR_RED "/" S_COLOR_MAGENTA "%s\n", color, target1st->client->pers.netname, (target1st->client->sess.sessionTeam == TEAM_RED) ? g_RQ3_team1name.string : g_RQ3_team2name.string, color, target2nd->client->pers.netname, (target2nd->client->sess.sessionTeam == TEAM_RED) ? g_RQ3_team1name.string : g_RQ3_team2name.string)); } } return target2nd; } static void CameraSwingAngle(gentity_t * ent) { gentity_t *target, *player; vec3_t player2target, vDiff; float best_angle, chase_diff, target_distance, target_yaw; float gamma, best_angle1, best_angle2, chase_diff1, chase_diff2; target = ent->client->camera->swing_secondry_target; player = ent->client->camera->swing_target; // no 2nd target if (target == NULL) // return to swing_angle == 0 { if ((fabs(ent->client->camera->swing_angle) < SWING_ANGLE_STEP)) ent->client->camera->swing_angle = 0; else if (ent->client->camera->swing_angle > 0) ent->client->camera->swing_angle = AngleMod180(ent->client->camera->swing_angle - SWING_ANGLE_STEP); else ent->client->camera->swing_angle = AngleMod180(ent->client->camera->swing_angle + SWING_ANGLE_STEP); return; } VectorSubtract(target->client->ps.origin, player->client->ps.origin, vDiff); target_distance = VectorLength(vDiff); vectoangles(vDiff, player2target); target_yaw = AngleMod180(player2target[YAW] - ent->client->camera->swing_last_viewangles[YAW]); if (target_distance > ent->client->camera->swing_distance) gamma = SWING_FOV_FACTOR * (acosf(ent->client->camera->swing_distance / target_distance)) * 180.0F / M_PI; else gamma = SWING_FOV_FACTOR * (90.0F - ((asinf(0.5 * target_distance / ent->client->camera->swing_distance)) * 180.0F / M_PI)); best_angle1 = AngleMod180(target_yaw - (180 - gamma)); best_angle2 = AngleMod180(target_yaw + (180 - gamma)); chase_diff1 = AngleDiff(ent->client->camera->swing_angle, best_angle1); chase_diff2 = AngleDiff(ent->client->camera->swing_angle, best_angle2); if (fabs(chase_diff1) < fabs(chase_diff2)) { chase_diff = chase_diff1; best_angle = best_angle1; } else { chase_diff = chase_diff2; best_angle = best_angle2; } // chase_diff is used to determine the direction to move to // in order to decrease/increase yaw separation if (fabs(chase_diff) < SWING_ANGLE_STEP) ent->client->camera->swing_angle = best_angle; else if (chase_diff > 0) ent->client->camera->swing_angle = AngleMod180(ent->client->camera->swing_angle - SWING_ANGLE_STEP); else ent->client->camera->swing_angle = AngleMod180(ent->client->camera->swing_angle + SWING_ANGLE_STEP); return; } static void CameraSwingViewangles(gentity_t * ent) { float desired_yaw, desired_pitch, yaw_diff, pitch_diff; // determine desired yaw and pitch if (ent->client->camera->swing_secondry_target) { vec3_t vDiff, target_angles; VectorSubtract(ent->client->camera->swing_secondry_target->client->ps.origin, ent->client->ps.origin, vDiff); vectoangles(vDiff, target_angles); desired_yaw = AngleMod180(target_angles[YAW] - (ent->client->camera->swing_angle + ent->client->ps.viewangles[YAW])) / 2; desired_yaw = AngleClamp(desired_yaw, 45); desired_pitch = AngleMod180(target_angles[PITCH] - ent->client->ps.viewangles[PITCH]) / 2; desired_pitch = AngleClamp(desired_pitch, 45); } else { desired_yaw = 0; desired_pitch = 0; } // move yaw yaw_diff = AngleMod180(ent->client->camera->swing_yaw - desired_yaw); if (fabs(yaw_diff) < SWING_VIEWANGLE_STEP) ent->client->camera->swing_yaw = desired_yaw; else if (yaw_diff > 0) ent->client->camera->swing_yaw = AngleMod180(ent->client->camera->swing_yaw - SWING_VIEWANGLE_STEP); else ent->client->camera->swing_yaw = AngleMod180(ent->client->camera->swing_yaw + SWING_VIEWANGLE_STEP); // move pitch pitch_diff = AngleMod180(ent->client->camera->swing_pitch - desired_pitch); if (fabs(pitch_diff) < SWING_VIEWANGLE_STEP) ent->client->camera->swing_pitch = desired_pitch; else if (pitch_diff > 0) ent->client->camera->swing_pitch = AngleMod180(ent->client->camera->swing_pitch - SWING_VIEWANGLE_STEP); else ent->client->camera->swing_pitch = AngleMod180(ent->client->camera->swing_pitch + SWING_VIEWANGLE_STEP); } void CameraSwingCycle(gentity_t * ent, int dir) { int clientnum = 0; int original = 0; if (dir != 1 && dir != -1) dir = 1; if (ent->client->camera->mode != CAMERA_MODE_SWING) { ent->client->camera->mode = CAMERA_MODE_SWING; // JBravo: yes, we know // CameraShowMode (ent); // start with current flic target if (ent->client->camera->flic_target != NULL && ent->client->camera->flic_target->client->pers.connected == CON_CONNECTED && ent->client->camera->flic_target->client->sess.sessionTeam != TEAM_SPECTATOR) { ent->client->camera->swing_target = ent->client->camera->flic_target; camera_begin(ent); return; } ent->client->camera->swing_target = NULL; } if (ent->client->camera->swing_target != NULL) { clientnum = ent->client->camera->swing_target->s.clientNum; original = clientnum; } do { clientnum += dir; if (clientnum >= level.maxclients) clientnum = 0; if (clientnum < 0) clientnum = level.maxclients - 1; // can only follow connected clients // can't follow another spectator if (level.clients[clientnum].pers.connected != CON_CONNECTED || level.clients[clientnum].sess.sessionTeam == TEAM_SPECTATOR) continue; // this is good, we can use it ent->client->camera->swing_target = &g_entities[clientnum]; camera_begin(ent); return; } while (clientnum != original); ent->client->camera->swing_target = NULL; } static void CameraSwingThink(gentity_t * ent) { vec3_t o, ownerv, goal, vDiff; gentity_t *target; vec3_t forward, right; trace_t trace; // vec3_t oldgoal; vec3_t angles; vec3_t viewangles; static vec3_t mins = { -4, -4, -4 }; static vec3_t maxs = { 4, 4, 4 }; // validate current target if (ent->client->camera->swing_target == NULL || (ent->client->camera->swing_target != NULL && (ent->client->camera->swing_target->client->pers.connected != CON_CONNECTED || ent->client->camera->swing_target->client->sess.sessionTeam == TEAM_SPECTATOR))) { // target is not valid: try the next client CameraSwingCycle(ent, 1); if (ent->client->camera->swing_target == NULL) return; } // we have a valid target: find secondry target target = ent->client->camera->swing_target; ent->client->camera->swing_secondry_target = CameraSwingTarget(ent); // update viewangles as long as target is alive if (target->client->ps.pm_type != PM_DEAD) { VectorCopy(target->client->ps.viewangles, ent->client->camera->swing_last_viewangles); } CameraSwingAngle(ent); ent->client->camera->swing_height = SWING_NOMINAL_HEIGHT; VectorCopy(target->client->ps.origin, ownerv); // VectorCopy(ent->client->ps.origin, oldgoal); VectorCopy(ent->client->camera->swing_last_viewangles, angles); angles[YAW] += ent->client->camera->swing_angle; angles[PITCH] = 0; AngleVectors(angles, forward, right, NULL); VectorNormalize(forward); VectorMA(ownerv, -ent->client->camera->swing_distance, forward, o); if (o[2] < target->client->ps.origin[2] + 20) o[2] = target->client->ps.origin[2] + 20; trap_Trace(&trace, ownerv, mins, maxs, o, ent->client->camera->swing_target->s.clientNum, CONTENTS_SOLID); if (trace.fraction != 1.0) { VectorCopy(trace.endpos, o); o[2] += (1.0 - trace.fraction) * 32; // try another trace to this position, because a tunnel may have the ceiling // close enough that this is poking out trap_Trace(&trace, ownerv, mins, maxs, o, ent->client->camera->swing_target->s.clientNum, CONTENTS_SOLID); VectorCopy(trace.endpos, o); VectorSubtract(ent->client->camera->swing_target->client->ps.origin, ent->client->ps.origin, vDiff); ent->client->camera->swing_distance = min(VectorLength(vDiff), SWING_NOMINAL_DISTANCE); } else { float distance = ent->client->camera->swing_distance - SWING_NOMINAL_DISTANCE; if (fabs(distance) < SWING_DISTANCE_STEP) ent->client->camera->swing_distance = SWING_NOMINAL_DISTANCE; else if (distance > 0) ent->client->camera->swing_distance -= SWING_DISTANCE_STEP; else ent->client->camera->swing_distance += SWING_DISTANCE_STEP; } VectorCopy(o, goal); // set camera angles VectorCopy(ent->client->camera->swing_last_viewangles, viewangles); if (ent->client->camera->swing_secondry_target != NULL) viewangles[PITCH] = 0; SetClientViewAngle(ent, viewangles); CameraSwingViewangles(ent); viewangles[PITCH] = AngleClamp(ent->client->camera->swing_pitch, 85); viewangles[YAW] += ent->client->camera->swing_yaw + ent->client->camera->swing_angle; SetClientViewAngle(ent, viewangles); // set camera position VectorCopy(goal, ent->client->ps.origin); } /* camera API */ void camera_init(void) { int i; for (i = 0; i < level.maxclients; i++) { cameras[i].mode = CAMERA_MODE_FLIC; cameras[i].flic_target = NULL; cameras[i].swing_target = NULL; cameras[i].swing_msg_time = 0; camera_begin(&g_entities[i]); } } void camera_shutdown(void) { } void camera_state_save(gclient_t * client) { int clientNum = client - level.clients; const char *s; const char *var; int mode = (int) cameras[clientNum].mode; int target = -1; gentity_t *ent = NULL; if (cameras[clientNum].mode == CAMERA_MODE_SWING) ent = cameras[clientNum].swing_target; if (ent != NULL) target = ent - g_entities; s = va("%i %i", mode, target); var = va("zcam%i", (int)(client - level.clients)); trap_Cvar_Set(var, s); } void camera_state_load(gclient_t * client) { int clientNum = client - level.clients; if (level.newSession == qtrue) { cameras[clientNum].mode = CAMERA_MODE_FLIC; cameras[clientNum].flic_target = NULL; cameras[clientNum].swing_target = NULL; } else { char s[MAX_STRING_CHARS]; const char *var; int mode; int target; var = va("zcam%i", clientNum); trap_Cvar_VariableStringBuffer(var, s, sizeof(s)); sscanf(s, "%i %i", &mode, &target); cameras[clientNum].mode = (camera_mode_t) mode; cameras[clientNum].swing_target = NULL; cameras[clientNum].flic_target = NULL; if (target >= 0 && cameras[clientNum].mode == CAMERA_MODE_SWING) cameras[clientNum].swing_target = &g_entities[target]; } } void camera_begin(gentity_t * ent) { int i; i = ent - g_entities; ent->client->camera = &cameras[i]; ent->client->camera->flic_watching_the_dead = qfalse; ent->client->camera->flic_watching_the_wall = qfalse; ent->client->camera->flic_xy_lag = DAMP_VALUE_XY; ent->client->camera->flic_z_lag = DAMP_VALUE_Z; ent->client->camera->flic_angle_lag = DAMP_ANGLE_Y; ent->client->camera->flic_last_move_time = 0; ent->client->camera->flic_last_switch_time = 0; ent->client->camera->swing_secondry_target = NULL; ent->client->camera->swing_distance = SWING_NOMINAL_DISTANCE; ent->client->camera->swing_height = 0.0F; ent->client->camera->swing_angle = 0.0F; ent->client->camera->swing_yaw = 0.0F; ent->client->camera->swing_pitch = 0.0F; VectorClear(ent->client->camera->swing_last_viewangles); } void camera_disconnect(gentity_t * ent) { int i; // reset camera state for disconnected clients CameraFlicBegin(ent); camera_state_save(ent->client); // force rethink on all cameras for (i = 0; i < level.maxclients; i++) if (level.clients[i].pers.connected == CON_CONNECTED && level.clients[i].sess.sessionTeam == TEAM_SPECTATOR) { if (level.clients[i].camera->mode == CAMERA_MODE_FLIC) { level.clients[i].camera->flic_last_move_time = level.time; if (level.clients[i].camera->flic_target == ent) { level.clients[i].camera->flic_target = NULL; level.clients[i].camera->swing_target = NULL; } } else if (level.clients[i].camera->mode == CAMERA_MODE_SWING) { if (level.clients[i].camera->swing_target == ent) { CameraSwingCycle(&g_entities[i], 1); } else if (level.clients[i].camera->swing_secondry_target == ent) { level.clients[i].camera->swing_secondry_target = NULL; } } } } void camera_think(gentity_t * ent) { // JBravo: removed zcam mode switching code. Handled AQ style in g_active.c if (NumPlayers() == 0) { CameraStaticThink(ent); return; } switch (ent->client->camera->mode) { case CAMERA_MODE_FLIC: CameraFlicThink(ent); break; case CAMERA_MODE_SWING: CameraSwingThink(ent); break; default: break; } } void camera_cmd(gentity_t * ent) { if (trap_Argc() == 2) { char arg[MAX_STRING_CHARS]; trap_Argv(1, arg, sizeof(arg)); if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) { if (Q_stricmp(arg, "prev") == 0) CameraSwingCycle(ent, -1); else if (Q_stricmp(arg, "next") == 0) CameraSwingCycle(ent, 1); else if (Q_stricmp(arg, "flic") == 0) { if (ent->client->camera->mode != CAMERA_MODE_FLIC) CameraFlicBegin(ent); } else if (Q_stricmp(arg, "swing") == 0) { if (ent->client->camera->mode != CAMERA_MODE_SWING) CameraSwingCycle(ent, 1); } } else { if (Q_stricmp(arg, "flic") == 0) ent->client->camera->mode = CAMERA_MODE_FLIC; else if (Q_stricmp(arg, "swing") == 0) ent->client->camera->mode = CAMERA_MODE_SWING; } } }