mirror of
https://git.code.sf.net/p/quake/quakeforge-old
synced 2024-11-12 23:44:21 +00:00
47bed98da1
How view height changes in QW are passed from server to client HAS CHANGED and you will need to update both client and server for things not to act weird. You may also need to distclean before rebuilding..
1097 lines
25 KiB
C
1097 lines
25 KiB
C
/*
|
|
view.c
|
|
|
|
player eye positioning
|
|
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
Copyright (C) 1999,2000 contributors of the QuakeForge project
|
|
Please see the file "AUTHORS" for a list of contributors
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
$Id$
|
|
*/
|
|
|
|
#include <quakedef.h>
|
|
#include <r_local.h>
|
|
#include <draw.h>
|
|
#include <mathlib.h>
|
|
#include <qtypes.h>
|
|
#include <qstructs.h>
|
|
#include <cmd.h>
|
|
#include <screen.h>
|
|
#include <console.h>
|
|
#include <cvar.h>
|
|
|
|
extern int onground;
|
|
|
|
/*
|
|
|
|
The view is allowed to move slightly from it's true position for bobbing,
|
|
but if it exceeds 8 pixels linear distance (spherical, not box), the list of
|
|
entities sent from the server may not include everything in the pvs, especially
|
|
when crossing a water boudnary.
|
|
|
|
*/
|
|
|
|
cvar_t *lcd_x; // FIXME: make this work sometime...
|
|
cvar_t *lcd_yaw;
|
|
|
|
cvar_t *scr_ofsx;
|
|
cvar_t *scr_ofsy;
|
|
cvar_t *scr_ofsz;
|
|
|
|
cvar_t *cl_rollspeed;
|
|
cvar_t *cl_rollangle;
|
|
|
|
cvar_t *cl_bob;
|
|
cvar_t *cl_bobcycle;
|
|
cvar_t *cl_bobup;
|
|
|
|
cvar_t *v_kicktime;
|
|
cvar_t *v_kickroll;
|
|
cvar_t *v_kickpitch;
|
|
|
|
cvar_t *v_iyaw_cycle;
|
|
cvar_t *v_iroll_cycle;
|
|
cvar_t *v_ipitch_cycle;
|
|
cvar_t *v_iyaw_level;
|
|
cvar_t *v_iroll_level;
|
|
cvar_t *v_ipitch_level;
|
|
|
|
cvar_t *v_idlescale;
|
|
|
|
cvar_t *crosshair;
|
|
cvar_t *crosshaircolor;
|
|
|
|
cvar_t *cl_crossx;
|
|
cvar_t *cl_crossy;
|
|
|
|
cvar_t *gl_cshiftpercent;
|
|
|
|
cvar_t *v_contentblend;
|
|
|
|
float v_dmg_time, v_dmg_roll, v_dmg_pitch;
|
|
|
|
extern int in_forward, in_forward2, in_back;
|
|
|
|
frame_t *view_frame;
|
|
player_state_t *view_message;
|
|
|
|
/*
|
|
V_CalcRoll
|
|
|
|
(desc)
|
|
*/
|
|
float
|
|
V_CalcRoll (vec3_t angles, vec3_t velocity)
|
|
{
|
|
vec3_t forward, right, up;
|
|
float sign;
|
|
float side;
|
|
float value;
|
|
|
|
AngleVectors (angles, forward, right, up);
|
|
side = DotProduct (velocity, right);
|
|
sign = side < 0 ? -1 : 1;
|
|
side = fabs(side);
|
|
|
|
value = cl_rollangle->value;
|
|
|
|
if (side < cl_rollspeed->value)
|
|
side = side * value / cl_rollspeed->value;
|
|
else
|
|
side = value;
|
|
|
|
return side*sign;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
V_CalcBob
|
|
|
|
(desc)
|
|
*/
|
|
float
|
|
V_CalcBob ( void )
|
|
{
|
|
#ifdef QUAKEWORLD
|
|
static double bobtime;
|
|
static float bob;
|
|
#else
|
|
float bob;
|
|
#endif
|
|
float cycle;
|
|
|
|
#ifdef QUAKEWORLD
|
|
if (cl.spectator)
|
|
return 0;
|
|
|
|
if (onground == -1)
|
|
return bob; // just use old value
|
|
|
|
bobtime += host_frametime;
|
|
cycle = bobtime - (int)(bobtime/cl_bobcycle->value)*cl_bobcycle->value;
|
|
#else
|
|
cycle = cl.time - (int)(cl.time/cl_bobcycle->value)*cl_bobcycle->value;
|
|
#endif
|
|
cycle /= cl_bobcycle->value;
|
|
if (cycle < cl_bobup->value)
|
|
cycle = M_PI * cycle / cl_bobup->value;
|
|
else
|
|
cycle = M_PI + M_PI*(cycle-cl_bobup->value)/(1.0 - cl_bobup->value);
|
|
|
|
// bob is proportional to [simulated] velocity in the xy plane
|
|
// (don't count Z, or jumping messes it up)
|
|
|
|
#ifdef QUAKEWORLD
|
|
bob = sqrt(cl.simvel[0]*cl.simvel[0] + cl.simvel[1]*cl.simvel[1]) * cl_bob->value;
|
|
#else
|
|
bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob->value;
|
|
#endif
|
|
bob = bob*0.3 + bob*0.7*sin(cycle);
|
|
bob = bound( -7, bob, 4);
|
|
return bob;
|
|
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
|
|
|
|
cvar_t *v_centermove;
|
|
cvar_t *v_centerspeed;
|
|
|
|
void
|
|
V_StartPitchDrift ( void )
|
|
{
|
|
if (cl.laststop == cl.time) {
|
|
return; // something else is keeping it from drifting
|
|
}
|
|
|
|
if (cl.nodrift || !cl.pitchvel) {
|
|
cl.pitchvel = v_centerspeed->value;
|
|
cl.nodrift = false;
|
|
cl.driftmove = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
V_StopPitchDrift (void)
|
|
{
|
|
cl.laststop = cl.time;
|
|
cl.nodrift = true;
|
|
cl.pitchvel = 0;
|
|
}
|
|
|
|
/*
|
|
V_DriftPitch
|
|
|
|
Move client pitch angle towards cl.idealpitch sent by the server.
|
|
|
|
If the user is adjusting pitch manually, either with lookup/lookdown,
|
|
mlook and mouse, or klook and keyboard, pitch drifting is constantly
|
|
stopped.
|
|
|
|
Drifting is enabled when the center view key is hit, mlook is released
|
|
and lookspring is nonzero, or when
|
|
*/
|
|
void
|
|
V_DriftPitch ( void )
|
|
{
|
|
float delta, move;
|
|
|
|
#ifdef QUAKEWORLD
|
|
if (view_message->onground == -1 || cls.demoplayback ) {
|
|
#else
|
|
if (noclip_anglehack || !cl.onground || cls.demoplayback ) {
|
|
#endif
|
|
cl.driftmove = 0;
|
|
cl.pitchvel = 0;
|
|
return;
|
|
}
|
|
|
|
// don't count small mouse motion
|
|
if ( cl.nodrift ) {
|
|
#ifdef QUAKEWORLD
|
|
if ( fabs(cl.frames[(cls.netchan.outgoing_sequence-1)&UPDATE_MASK].cmd.forwardmove) < 200)
|
|
#else
|
|
if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed->value)
|
|
#endif
|
|
cl.driftmove = 0;
|
|
else
|
|
cl.driftmove += host_frametime;
|
|
|
|
if ( cl.driftmove > v_centermove->value) {
|
|
V_StartPitchDrift ();
|
|
}
|
|
return;
|
|
}
|
|
|
|
delta = cl.idealpitch - cl.viewangles[PITCH];
|
|
|
|
if ( !delta ) {
|
|
cl.pitchvel = 0;
|
|
return;
|
|
}
|
|
|
|
move = host_frametime * cl.pitchvel;
|
|
cl.pitchvel += host_frametime * v_centerspeed->value;
|
|
|
|
//Con_Printf ("move: %f (%f)\n", move, host_frametime);
|
|
|
|
if (delta > 0) {
|
|
if (move > delta)
|
|
{
|
|
cl.pitchvel = 0;
|
|
move = delta;
|
|
}
|
|
cl.viewangles[PITCH] += move;
|
|
} else if (delta < 0) {
|
|
if (move > -delta) {
|
|
cl.pitchvel = 0;
|
|
move = -delta;
|
|
}
|
|
cl.viewangles[PITCH] -= move;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
PALETTE FLASHES
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
|
|
cshift_t cshift_empty = { {130,80,50}, 0 };
|
|
cshift_t cshift_water = { {130,80,50}, 128 };
|
|
cshift_t cshift_slime = { {0,25,5}, 150 };
|
|
cshift_t cshift_lava = { {255,80,0}, 150 };
|
|
|
|
cvar_t *v_gamma;
|
|
|
|
byte gammatable[256]; // palette is sent through this
|
|
|
|
byte ramps[3][256];
|
|
float v_blend[4]; // rgba 0.0 - 1.0
|
|
|
|
void
|
|
BuildGammaTable ( float g )
|
|
{
|
|
int i, inf;
|
|
|
|
if (g == 1.0) {
|
|
for (i=0 ; i<256 ; i++)
|
|
gammatable[i] = i;
|
|
return;
|
|
}
|
|
|
|
for (i=0 ; i<256 ; i++) {
|
|
inf = 255 * pow ( (i+0.5)/255.5 , g ) + 0.5;
|
|
inf = bound(0, inf, 255);
|
|
gammatable[i] = inf;
|
|
}
|
|
}
|
|
|
|
/*
|
|
V_CheckGamma
|
|
|
|
(desc)
|
|
*/
|
|
qboolean
|
|
V_CheckGamma ( void )
|
|
{
|
|
static float oldgammavalue;
|
|
|
|
if (v_gamma->value == oldgammavalue)
|
|
return false;
|
|
oldgammavalue = v_gamma->value;
|
|
|
|
BuildGammaTable (v_gamma->value);
|
|
vid.recalc_refdef = 1; // force a surface cache flush
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
V_ParseDamage
|
|
|
|
(desc)
|
|
*/
|
|
void
|
|
V_ParseDamage ( void )
|
|
{
|
|
int armor, blood;
|
|
vec3_t from;
|
|
int i;
|
|
vec3_t forward, right, up;
|
|
#ifdef UQUAKE
|
|
entity_t *ent;
|
|
#endif
|
|
float side;
|
|
float count;
|
|
|
|
armor = MSG_ReadByte ();
|
|
blood = MSG_ReadByte ();
|
|
for (i=0 ; i<3 ; i++)
|
|
from[i] = MSG_ReadCoord ();
|
|
|
|
count = blood*0.5 + armor*0.5;
|
|
count = min(10, count);
|
|
|
|
cl.faceanimtime = cl.time + 0.2; // but sbar face into pain frame
|
|
|
|
cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
|
|
cl.cshifts[CSHIFT_DAMAGE].percent = bound(0, cl.cshifts[CSHIFT_DAMAGE].percent, 150);
|
|
|
|
if ( armor > blood ) {
|
|
cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
|
|
cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
|
|
cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
|
|
} else if ( armor ) {
|
|
cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
|
|
cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
|
|
cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
|
|
} else {
|
|
cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
|
|
cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
|
|
cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
|
|
}
|
|
|
|
// calculate view angle kicks
|
|
#ifdef QUAKEWORLD
|
|
VectorSubtract (from, cl.simorg, from);
|
|
#else
|
|
ent = &cl_entities[cl.playernum + 1];
|
|
|
|
VectorSubtract (from, ent->origin, from);
|
|
#endif
|
|
VectorNormalize (from);
|
|
|
|
#ifdef QUAKEWORLD
|
|
AngleVectors (cl.simangles, forward, right, up);
|
|
#else
|
|
AngleVectors (ent->angles, forward, right, up);
|
|
#endif
|
|
|
|
side = DotProduct (from, right);
|
|
v_dmg_roll = count*side*v_kickroll->value;
|
|
|
|
side = DotProduct (from, forward);
|
|
v_dmg_pitch = count*side*v_kickpitch->value;
|
|
|
|
v_dmg_time = v_kicktime->value;
|
|
}
|
|
|
|
|
|
/*
|
|
V_cshift_f
|
|
|
|
(desc)
|
|
*/
|
|
void
|
|
V_cshift_f ( void )
|
|
{
|
|
int i;
|
|
|
|
for ( i=0 ; i<3 ; i++ )
|
|
cshift_empty.destcolor[i] = atoi(Cmd_Argv(i+1));
|
|
cshift_empty.percent = atoi(Cmd_Argv(4));
|
|
}
|
|
|
|
|
|
/*
|
|
V_BonusFlash_f
|
|
|
|
When you run over an item, the server sends this command
|
|
*/
|
|
void
|
|
V_BonusFlash_f ( void )
|
|
{
|
|
cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
|
|
cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
|
|
cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
|
|
cl.cshifts[CSHIFT_BONUS].percent = 50;
|
|
}
|
|
|
|
/*
|
|
V_SetContentsColor
|
|
|
|
Underwater, lava, etc each has a color shift
|
|
*/
|
|
void
|
|
V_SetContentsColor (int contents)
|
|
{
|
|
if (!v_contentblend->value) {
|
|
cl.cshifts[CSHIFT_CONTENTS] = cshift_empty;
|
|
return;
|
|
}
|
|
|
|
switch (contents) {
|
|
case CONTENTS_EMPTY:
|
|
cl.cshifts[CSHIFT_CONTENTS] = cshift_empty;
|
|
break;
|
|
case CONTENTS_LAVA:
|
|
cl.cshifts[CSHIFT_CONTENTS] = cshift_lava;
|
|
break;
|
|
case CONTENTS_SOLID:
|
|
case CONTENTS_SLIME:
|
|
cl.cshifts[CSHIFT_CONTENTS] = cshift_slime;
|
|
break;
|
|
default:
|
|
cl.cshifts[CSHIFT_CONTENTS] = cshift_water;
|
|
}
|
|
|
|
if (v_contentblend->value > 0 && v_contentblend->value < 1)
|
|
cl.cshifts[CSHIFT_CONTENTS].percent *= v_contentblend->value;
|
|
}
|
|
|
|
/*
|
|
V_CalcPowerupCshift
|
|
|
|
Calculate the color to shade the view under powerups.
|
|
*/
|
|
void
|
|
V_CalcPowerupCshift ( void )
|
|
{
|
|
if (cl.stats[STAT_ITEMS] & IT_QUAD) {
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
|
|
cl.cshifts[CSHIFT_POWERUP].percent = 30;
|
|
} else if (cl.stats[STAT_ITEMS] & IT_SUIT) {
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
|
|
cl.cshifts[CSHIFT_POWERUP].percent = 20;
|
|
} else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) {
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
|
|
cl.cshifts[CSHIFT_POWERUP].percent = 100;
|
|
} else if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) {
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
|
|
cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
|
|
cl.cshifts[CSHIFT_POWERUP].percent = 30;
|
|
} else {
|
|
cl.cshifts[CSHIFT_POWERUP].percent = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
V_CalcBlend
|
|
*/
|
|
void
|
|
V_CalcBlend ( void )
|
|
{
|
|
float r, g, b, a, a2;
|
|
int j;
|
|
|
|
r = 0;
|
|
g = 0;
|
|
b = 0;
|
|
a = 0;
|
|
|
|
for (j=0 ; j<NUM_CSHIFTS ; j++) {
|
|
if (!gl_cshiftpercent->value)
|
|
continue;
|
|
|
|
a2 = ((cl.cshifts[j].percent * gl_cshiftpercent->value) / 100.0) / 255.0;
|
|
|
|
if (!a2)
|
|
continue;
|
|
a = a + a2*(1-a);
|
|
// Con_Printf ("j:%i a:%f\n", j, a);
|
|
a2 = a2/a;
|
|
r = r*(1-a2) + cl.cshifts[j].destcolor[0]*a2;
|
|
g = g*(1-a2) + cl.cshifts[j].destcolor[1]*a2;
|
|
b = b*(1-a2) + cl.cshifts[j].destcolor[2]*a2;
|
|
}
|
|
|
|
v_blend[0] = r/255.0;
|
|
v_blend[1] = g/255.0;
|
|
v_blend[2] = b/255.0;
|
|
v_blend[3] = bound(0.0, a, 1.0);
|
|
}
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
VIEW RENDERING
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
float
|
|
angledelta ( float a )
|
|
{
|
|
a = anglemod(a);
|
|
if (a > 180)
|
|
a -= 360;
|
|
return a;
|
|
}
|
|
|
|
/*
|
|
CalcGunAngle
|
|
|
|
Calculate gun angle. :)
|
|
*/
|
|
void
|
|
CalcGunAngle ( void )
|
|
{
|
|
float yaw, pitch, move;
|
|
static float oldyaw = 0;
|
|
static float oldpitch = 0;
|
|
|
|
yaw = r_refdef.viewangles[YAW];
|
|
pitch = -r_refdef.viewangles[PITCH];
|
|
|
|
yaw = angledelta(yaw - r_refdef.viewangles[YAW]) * 0.4;
|
|
yaw = bound(-10, yaw, 10);
|
|
|
|
pitch = angledelta(-pitch - r_refdef.viewangles[PITCH]) * 0.4;
|
|
pitch = bound(-10, pitch, 10);
|
|
|
|
move = host_frametime*20;
|
|
if ( yaw > oldyaw ) {
|
|
if (oldyaw + move < yaw)
|
|
yaw = oldyaw + move;
|
|
} else {
|
|
if (oldyaw - move > yaw)
|
|
yaw = oldyaw - move;
|
|
}
|
|
|
|
if ( pitch > oldpitch ) {
|
|
if (oldpitch + move < pitch)
|
|
pitch = oldpitch + move;
|
|
} else {
|
|
if (oldpitch - move > pitch)
|
|
pitch = oldpitch - move;
|
|
}
|
|
|
|
oldyaw = yaw;
|
|
oldpitch = pitch;
|
|
|
|
cl.viewent.angles[YAW] = r_refdef.viewangles[YAW] + yaw;
|
|
cl.viewent.angles[PITCH] = - (r_refdef.viewangles[PITCH] + pitch);
|
|
#ifdef UQUAKE
|
|
cl.viewent.angles[ROLL] -= v_idlescale->value * sin(cl.time*v_iroll_cycle->value) * v_iroll_level->value;
|
|
cl.viewent.angles[PITCH] -= v_idlescale->value * sin(cl.time*v_ipitch_cycle->value) * v_ipitch_level->value;
|
|
cl.viewent.angles[YAW] -= v_idlescale->value * sin(cl.time*v_iyaw_cycle->value) * v_iyaw_level->value;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
V_BoundOffsets
|
|
|
|
absolutely bound refresh reletive to entity clipping hull so view can
|
|
never be inside a solid wall
|
|
*/
|
|
void
|
|
V_BoundOffsets ( void )
|
|
{
|
|
#ifdef UQUAKE
|
|
entity_t *ent;
|
|
|
|
ent = &cl_entities[cl.playernum + 1];
|
|
#endif
|
|
|
|
#ifdef QUAKEWORLD
|
|
if (r_refdef.vieworg[0] < cl.simorg[0] - 14)
|
|
r_refdef.vieworg[0] = cl.simorg[0] - 14;
|
|
else if (r_refdef.vieworg[0] > cl.simorg[0] + 14)
|
|
r_refdef.vieworg[0] = cl.simorg[0] + 14;
|
|
if (r_refdef.vieworg[1] < cl.simorg[1] - 14)
|
|
r_refdef.vieworg[1] = cl.simorg[1] - 14;
|
|
else if (r_refdef.vieworg[1] > cl.simorg[1] + 14)
|
|
r_refdef.vieworg[1] = cl.simorg[1] + 14;
|
|
if (r_refdef.vieworg[2] < cl.simorg[2] - 22)
|
|
r_refdef.vieworg[2] = cl.simorg[2] - 22;
|
|
else if (r_refdef.vieworg[2] > cl.simorg[2] + 30)
|
|
r_refdef.vieworg[2] = cl.simorg[2] + 30;
|
|
#else
|
|
if (r_refdef.vieworg[0] < ent->origin[0] - 14)
|
|
r_refdef.vieworg[0] = ent->origin[0] - 14;
|
|
else if (r_refdef.vieworg[0] > ent->origin[0] + 14)
|
|
r_refdef.vieworg[0] = ent->origin[0] + 14;
|
|
if (r_refdef.vieworg[1] < ent->origin[1] - 14)
|
|
r_refdef.vieworg[1] = ent->origin[1] - 14;
|
|
else if (r_refdef.vieworg[1] > ent->origin[1] + 14)
|
|
r_refdef.vieworg[1] = ent->origin[1] + 14;
|
|
if (r_refdef.vieworg[2] < ent->origin[2] - 22)
|
|
r_refdef.vieworg[2] = ent->origin[2] - 22;
|
|
else if (r_refdef.vieworg[2] > ent->origin[2] + 30)
|
|
r_refdef.vieworg[2] = ent->origin[2] + 30;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
V_AddIdle
|
|
|
|
Idle swaying
|
|
*/
|
|
void
|
|
V_AddIdle ( void )
|
|
{
|
|
r_refdef.viewangles[ROLL] += v_idlescale->value * sin(cl.time*v_iroll_cycle->value) * v_iroll_level->value;
|
|
r_refdef.viewangles[PITCH] += v_idlescale->value * sin(cl.time*v_ipitch_cycle->value) * v_ipitch_level->value;
|
|
r_refdef.viewangles[YAW] += v_idlescale->value * sin(cl.time*v_iyaw_cycle->value) * v_iyaw_level->value;
|
|
#ifdef QUAKEWORLD
|
|
cl.viewent.angles[ROLL] -= v_idlescale->value * sin(cl.time*v_iroll_cycle->value) * v_iroll_level->value;
|
|
cl.viewent.angles[PITCH] -= v_idlescale->value * sin(cl.time*v_ipitch_cycle->value) * v_ipitch_level->value;
|
|
cl.viewent.angles[YAW] -= v_idlescale->value * sin(cl.time*v_iyaw_cycle->value) * v_iyaw_level->value;
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
V_CalcViewRoll
|
|
|
|
Roll is induced by movement and damage
|
|
*/
|
|
void
|
|
V_CalcViewRoll (void)
|
|
{
|
|
float side;
|
|
|
|
#ifdef QUAKEWORLD
|
|
side = V_CalcRoll (cl.simangles, cl.simvel);
|
|
#else
|
|
side = V_CalcRoll (cl_entities[cl.playernum + 1].angles, cl.velocity);
|
|
#endif
|
|
r_refdef.viewangles[ROLL] += side;
|
|
|
|
if (v_dmg_time > 0) {
|
|
r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime->value*v_dmg_roll;
|
|
r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime->value*v_dmg_pitch;
|
|
v_dmg_time -= host_frametime;
|
|
}
|
|
|
|
#ifdef UQUAKE
|
|
if (cl.stats[STAT_HEALTH] <= 0)
|
|
{
|
|
r_refdef.viewangles[ROLL] = 80; // dead view angle
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
V_CalcIntermissionRefdef
|
|
|
|
(desc)
|
|
*/
|
|
void
|
|
V_CalcIntermissionRefdef ( void )
|
|
{
|
|
entity_t *view;
|
|
#ifdef UQUAKE
|
|
entity_t *ent;
|
|
#endif
|
|
float old;
|
|
|
|
// view is the weapon model
|
|
#ifdef UQUAKE
|
|
// ent is the player model (visible when out of body)
|
|
ent = &cl_entities[cl.playernum + 1];
|
|
// view is the weapon model (only visible from inside body)
|
|
#endif
|
|
view = &cl.viewent;
|
|
|
|
#ifdef QUAKEWORLD
|
|
VectorCopy (cl.simorg, r_refdef.vieworg);
|
|
VectorCopy (cl.simangles, r_refdef.viewangles);
|
|
#else
|
|
VectorCopy (ent->origin, r_refdef.vieworg);
|
|
VectorCopy (ent->angles, r_refdef.viewangles);
|
|
#endif
|
|
view->model = NULL;
|
|
|
|
// allways idle in intermission
|
|
old = v_idlescale->value;
|
|
v_idlescale->value = 1;
|
|
V_AddIdle ();
|
|
v_idlescale->value = old;
|
|
}
|
|
|
|
/*
|
|
V_CalcRefdef
|
|
|
|
(desc)
|
|
*/
|
|
void
|
|
V_CalcRefdef ( void )
|
|
{
|
|
entity_t *view;
|
|
#ifdef UQUAKE
|
|
entity_t *ent;
|
|
vec3_t angles;
|
|
#endif
|
|
int i;
|
|
vec3_t forward, right, up;
|
|
float bob;
|
|
static float oldz = 0;
|
|
#ifdef QUAKEWORLD
|
|
int zofs = 22;
|
|
|
|
if (cl.stdver)
|
|
zofs = cl.stats[STAT_VIEWHEIGHT];
|
|
#endif
|
|
V_DriftPitch ();
|
|
|
|
#ifdef UQUAKE
|
|
// ent is the player model (visible when out of body)
|
|
ent = &cl_entities[cl.playernum + 1];
|
|
#endif
|
|
// view is the weapon model (only visible from inside body)
|
|
view = &cl.viewent;
|
|
|
|
#ifdef UQUAKE
|
|
// transform the view offset by the model's matrix to get the offset from
|
|
// model origin for the view
|
|
ent->angles[YAW] = cl.viewangles[YAW]; // the model should face
|
|
// the view dir
|
|
ent->angles[PITCH] = -cl.viewangles[PITCH]; // the model should face
|
|
// the view dir
|
|
|
|
|
|
#endif
|
|
bob = V_CalcBob ();
|
|
|
|
#ifdef QUAKEWORLD
|
|
// refresh position from simulated origin
|
|
VectorCopy (cl.simorg, r_refdef.vieworg);
|
|
|
|
r_refdef.vieworg[2] += bob;
|
|
#else
|
|
// refresh position
|
|
VectorCopy (ent->origin, r_refdef.vieworg);
|
|
r_refdef.vieworg[2] += cl.viewheight + bob;
|
|
#endif
|
|
|
|
// never let it sit exactly on a node line, because a water plane can
|
|
// dissapear when viewed with the eye exactly on it.
|
|
#ifdef QUAKEWORLD
|
|
// the server protocol only specifies to 1/8 pixel, so add 1/16 in each axis
|
|
r_refdef.vieworg[0] += 1.0/16;
|
|
r_refdef.vieworg[1] += 1.0/16;
|
|
r_refdef.vieworg[2] += 1.0/16;
|
|
#else
|
|
// the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis
|
|
r_refdef.vieworg[0] += 1.0/32;
|
|
r_refdef.vieworg[1] += 1.0/32;
|
|
r_refdef.vieworg[2] += 1.0/32;
|
|
#endif
|
|
|
|
#ifdef QUAKEWORLD
|
|
VectorCopy (cl.simangles, r_refdef.viewangles);
|
|
#else
|
|
VectorCopy (cl.viewangles, r_refdef.viewangles);
|
|
#endif
|
|
V_CalcViewRoll ();
|
|
V_AddIdle ();
|
|
|
|
// offsets
|
|
#ifdef QUAKEWORLD
|
|
if (view_message->flags & PF_GIB)
|
|
r_refdef.vieworg[2] += 8; // gib view height
|
|
else if (view_message->flags & PF_DEAD)
|
|
r_refdef.vieworg[2] -= 16; // corpse view height
|
|
else
|
|
r_refdef.vieworg[2] += zofs; // view height
|
|
|
|
if (view_message->flags & PF_DEAD) // PF_GIB will also set PF_DEAD
|
|
r_refdef.viewangles[ROLL] = 80; // dead view angle
|
|
#else
|
|
// offsets
|
|
angles[PITCH] = -ent->angles[PITCH]; // because entity pitches are
|
|
// actually backward
|
|
angles[YAW] = ent->angles[YAW];
|
|
angles[ROLL] = ent->angles[ROLL];
|
|
#endif
|
|
|
|
#ifdef UQUAKE
|
|
AngleVectors (angles, forward, right, up);
|
|
#endif
|
|
|
|
#ifdef QUAKEWORLD
|
|
// offsets
|
|
AngleVectors (cl.simangles, forward, right, up);
|
|
#else
|
|
for (i=0 ; i<3 ; i++)
|
|
r_refdef.vieworg[i] += scr_ofsx->value*forward[i]
|
|
+ scr_ofsy->value*right[i]
|
|
+ scr_ofsz->value*up[i];
|
|
|
|
#endif
|
|
|
|
#ifdef UQUAKE
|
|
V_BoundOffsets ();
|
|
#endif
|
|
// set up gun position
|
|
#ifdef QUAKEWORLD
|
|
VectorCopy (cl.simangles, view->angles);
|
|
#else
|
|
VectorCopy (cl.viewangles, view->angles);
|
|
#endif
|
|
|
|
CalcGunAngle ();
|
|
|
|
#ifdef QUAKEWORLD
|
|
VectorCopy (cl.simorg, view->origin);
|
|
view->origin[2] += zofs;
|
|
#else
|
|
VectorCopy (ent->origin, view->origin);
|
|
view->origin[2] += cl.viewheight;
|
|
#endif
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
view->origin[i] += forward[i]*bob*0.4;
|
|
// view->origin[i] += right[i]*bob*0.4;
|
|
// view->origin[i] += up[i]*bob*0.8;
|
|
}
|
|
view->origin[2] += bob;
|
|
|
|
// fudge position around to keep amount of weapon visible
|
|
// roughly equal with different FOV
|
|
if (scr_viewsize->value == 110)
|
|
view->origin[2] += 1;
|
|
else if (scr_viewsize->value == 100)
|
|
view->origin[2] += 2;
|
|
else if (scr_viewsize->value == 90)
|
|
view->origin[2] += 1;
|
|
else if (scr_viewsize->value == 80)
|
|
view->origin[2] += 0.5;
|
|
|
|
#ifdef QUAKEWORLD
|
|
if (view_message->flags & (PF_GIB|PF_DEAD) )
|
|
view->model = NULL;
|
|
else
|
|
view->model = cl.model_precache[cl.stats[STAT_WEAPON]];
|
|
view->frame = view_message->weaponframe;
|
|
#else
|
|
view->model = cl.model_precache[cl.stats[STAT_WEAPON]];
|
|
view->frame = cl.stats[STAT_WEAPONFRAME];
|
|
#endif
|
|
view->colormap = vid.colormap;
|
|
|
|
// set up the refresh position
|
|
r_refdef.viewangles[PITCH] += cl.punchangle;
|
|
|
|
// smooth out stair step ups
|
|
#ifdef QUAKEWORLD
|
|
if ( (view_message->onground != -1) && (cl.simorg[2] - oldz > 0) ) {
|
|
float steptime;
|
|
|
|
steptime = host_frametime;
|
|
|
|
oldz += steptime * 80;
|
|
if (oldz > cl.simorg[2])
|
|
oldz = cl.simorg[2];
|
|
if (cl.simorg[2] - oldz > 12)
|
|
oldz = cl.simorg[2] - 12;
|
|
r_refdef.vieworg[2] += oldz - cl.simorg[2];
|
|
view->origin[2] += oldz - cl.simorg[2];
|
|
} else {
|
|
oldz = cl.simorg[2];
|
|
}
|
|
#else
|
|
if (cl.onground && ent->origin[2] - oldz > 0) {
|
|
float steptime;
|
|
|
|
steptime = cl.time - cl.oldtime;
|
|
if (steptime < 0)
|
|
//FIXME I_Error ("steptime < 0");
|
|
steptime = 0;
|
|
|
|
oldz += steptime * 80;
|
|
if (oldz > ent->origin[2])
|
|
oldz = ent->origin[2];
|
|
if (ent->origin[2] - oldz > 12)
|
|
oldz = ent->origin[2] - 12;
|
|
r_refdef.vieworg[2] += oldz - ent->origin[2];
|
|
view->origin[2] += oldz - ent->origin[2];
|
|
}
|
|
|
|
oldz = ent->origin[2];
|
|
if (cl_chasecam->value)
|
|
Chase_Update ();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
DropPunchAngle
|
|
|
|
(desc)
|
|
*/
|
|
#ifdef QUAKEWORLD
|
|
void
|
|
DropPunchAngle (void)
|
|
{
|
|
cl.punchangle -= 10*host_frametime;
|
|
cl.punchangle = max(cl.punchangle, 0);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
V_RenderView
|
|
|
|
The player's clipping box goes from (-16 -16 -24) to (16 16 32) from
|
|
the entity origin, so any view position inside that will be valid
|
|
*/
|
|
extern vrect_t scr_vrect;
|
|
|
|
void
|
|
V_RenderView (void)
|
|
{
|
|
#ifdef QUAKEWORLD
|
|
// if (cl.simangles[ROLL])
|
|
// Sys_Error ("cl.simangles[ROLL]"); // DEBUG
|
|
cl.simangles[ROLL] = 0; // FIXME
|
|
#endif
|
|
|
|
if (cls.state != ca_active)
|
|
return;
|
|
|
|
#ifdef QUAKEWORLD
|
|
view_frame = &cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK];
|
|
view_message = &view_frame->playerstate[cl.playernum];
|
|
#else
|
|
// don't allow cheats in multiplayer
|
|
if (cl.maxclients > 1) {
|
|
Cvar_Set (scr_ofsx, "0");
|
|
Cvar_Set (scr_ofsy, "0");
|
|
Cvar_Set (scr_ofsz, "0");
|
|
}
|
|
#endif
|
|
|
|
#ifdef QUAKEWORLD
|
|
DropPunchAngle ();
|
|
#endif
|
|
if (cl.intermission) { // intermission / finale rendering
|
|
V_CalcIntermissionRefdef ();
|
|
} else {
|
|
if (!cl.paused /* && (sv.maxclients > 1 || key_dest == key_game) */ )
|
|
V_CalcRefdef ();
|
|
}
|
|
|
|
R_PushDlights ();
|
|
#if 0
|
|
if (lcd_x->value) {
|
|
/*
|
|
render two interleaved views
|
|
*/
|
|
int i;
|
|
|
|
vid.rowbytes <<= 1;
|
|
vid.aspect *= 0.5;
|
|
|
|
r_refdef.viewangles[YAW] -= lcd_yaw->value;
|
|
for (i=0 ; i<3 ; i++)
|
|
r_refdef.vieworg[i] -= right[i]*lcd_x->value;
|
|
R_RenderView ();
|
|
|
|
vid.buffer += vid.rowbytes>>1;
|
|
|
|
R_PushDlights ();
|
|
|
|
r_refdef.viewangles[YAW] += lcd_yaw->value*2;
|
|
for (i=0 ; i<3 ; i++)
|
|
r_refdef.vieworg[i] += 2*right[i]*lcd_x->value;
|
|
R_RenderView ();
|
|
|
|
vid.buffer -= vid.rowbytes>>1;
|
|
|
|
r_refdef.vrect.height <<= 1;
|
|
|
|
vid.rowbytes >>= 1;
|
|
vid.aspect *= 2;
|
|
} else {
|
|
R_RenderView ();
|
|
}
|
|
#else
|
|
R_RenderView ();
|
|
#endif
|
|
|
|
if (crosshair->value)
|
|
Draw_Crosshair();
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
V_Init
|
|
|
|
Create and initialize view cvars and console commands
|
|
*/
|
|
void
|
|
V_Init ( void )
|
|
{
|
|
Cmd_AddCommand ("v_cshift", V_cshift_f);
|
|
Cmd_AddCommand ("bf", V_BonusFlash_f);
|
|
Cmd_AddCommand ("centerview", V_StartPitchDrift);
|
|
|
|
lcd_x = Cvar_Get ("lcd_x", "0", CVAR_NONE, "None");
|
|
lcd_yaw = Cvar_Get ("lcd_yaw", "0", CVAR_NONE, "None");
|
|
|
|
v_centermove = Cvar_Get ("v_centermove", "0.15", CVAR_NONE, "None");
|
|
v_centerspeed = Cvar_Get ("v_centerspeed", "500", CVAR_NONE, "None");
|
|
|
|
v_iyaw_cycle = Cvar_Get ("v_iyaw_cycle", "2", CVAR_NONE, "None");
|
|
v_iroll_cycle = Cvar_Get ("v_iroll_cycle", "0.5", CVAR_NONE, "None");
|
|
v_ipitch_cycle = Cvar_Get ("v_ipitch_cycle", "1", CVAR_NONE, "None");
|
|
v_iyaw_level = Cvar_Get ("v_iyaw_level", "0.3", CVAR_NONE, "None");
|
|
v_iroll_level = Cvar_Get ("v_iroll_level", "0.1", CVAR_NONE, "None");
|
|
v_ipitch_level = Cvar_Get ("v_ipitch_level", "0.3", CVAR_NONE, "None");
|
|
|
|
v_contentblend = Cvar_Get ("v_contentblend", "1", CVAR_NONE, "Shift color in liquids");
|
|
|
|
v_idlescale = Cvar_Get ("v_idlescale", "0", CVAR_NONE, "None");
|
|
crosshaircolor = Cvar_Get ("crosshaircolor", "79", CVAR_ARCHIVE, "Crosshair Color");
|
|
crosshair = Cvar_Get ("crosshair", "0", CVAR_ARCHIVE, "Crosshair selection");
|
|
cl_crossx = Cvar_Get ("cl_crossx", "0", CVAR_ARCHIVE, "Crosshair X location");
|
|
cl_crossy = Cvar_Get ("cl_crossy", "0", CVAR_ARCHIVE, "Crosshair Y location");
|
|
gl_cshiftpercent = Cvar_Get ("gl_cshiftpercent", "100", CVAR_NONE, "Percentage of color shifting");
|
|
|
|
scr_ofsx = Cvar_Get ("scr_ofsx", "0", CVAR_NONE, "None");
|
|
scr_ofsy = Cvar_Get ("scr_ofsy", "0", CVAR_NONE, "None");
|
|
scr_ofsz = Cvar_Get ("scr_ofsz", "0", CVAR_NONE, "None");
|
|
|
|
cl_rollspeed = Cvar_Get ("cl_rollspeed", "200", CVAR_NONE, "None");
|
|
cl_rollangle = Cvar_Get ("cl_rollangle", "2.0", CVAR_NONE, "None");
|
|
cl_bob = Cvar_Get ("cl_bob", "0.02", CVAR_NONE, "None");
|
|
cl_bobcycle = Cvar_Get ("cl_bobcycle", "0.6", CVAR_NONE, "None");
|
|
cl_bobup = Cvar_Get ("cl_bobup", "0.5", CVAR_NONE, "None");
|
|
|
|
v_kicktime = Cvar_Get ("v_kicktime", "0.5", CVAR_NONE, "None");
|
|
v_kickroll = Cvar_Get ("v_kickroll", "0.6", CVAR_NONE, "None");
|
|
v_kickpitch = Cvar_Get ("v_kickpitch", "0.6", CVAR_NONE, "None");
|
|
v_gamma = Cvar_Get ("v_gamma", "1.0", CVAR_ARCHIVE, "Monitor gamma");
|
|
|
|
BuildGammaTable (v_gamma->value); // no gamma yet
|
|
}
|
|
|