reaction/code/game/zcam.c
2013-01-04 13:27:22 +00:00

1181 lines
34 KiB
C

//-----------------------------------------------------------------------------
//
// $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;
}
}
}