mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 13:47:58 +03:00

If the player model or the player german model is not valid, they will be reset to the default value. If the server doesn't have one or more of these models, force model feature will not work
1015 lines
32 KiB
C
1015 lines
32 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 2023 the OpenMoHAA team
|
|
|
|
This file is part of OpenMoHAA source code.
|
|
|
|
OpenMoHAA source code 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.
|
|
|
|
OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "cg_local.h"
|
|
#include "cg_parsemsg.h"
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
=================
|
|
CG_CalcVrect
|
|
|
|
Sets the coordinates of the rendered window
|
|
=================
|
|
*/
|
|
static void CG_CalcVrect(void)
|
|
{
|
|
int size;
|
|
|
|
// the intermission should allways be full screen
|
|
if (cg.snap->ps.pm_flags & PMF_INTERMISSION) {
|
|
size = 100;
|
|
} else {
|
|
// bound normal viewsize
|
|
if (cg_viewsize->integer < 30) {
|
|
cgi.Cvar_Set("viewsize", "30");
|
|
size = 30;
|
|
} else if (cg_viewsize->integer > 100) {
|
|
cgi.Cvar_Set("viewsize", "100");
|
|
size = 100;
|
|
} else {
|
|
size = cg_viewsize->integer;
|
|
}
|
|
}
|
|
cg.refdef.width = cgs.glconfig.vidWidth * size / 100;
|
|
cg.refdef.width &= ~1;
|
|
|
|
cg.refdef.height = cgs.glconfig.vidHeight * size / 100;
|
|
cg.refdef.height &= ~1;
|
|
|
|
cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width) / 2;
|
|
cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height) / 2;
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
/*
|
|
===============
|
|
CG_OffsetThirdPersonView
|
|
|
|
===============
|
|
*/
|
|
#define CAMERA_MINIMUM_DISTANCE 40
|
|
|
|
static void CG_OffsetThirdPersonView(void)
|
|
{
|
|
vec3_t forward;
|
|
vec3_t original_camera_position;
|
|
vec3_t new_vieworg;
|
|
trace_t trace;
|
|
vec3_t min, max;
|
|
float *look_offset;
|
|
float *target_angles;
|
|
float *target_position;
|
|
vec3_t delta;
|
|
vec3_t original_angles;
|
|
qboolean lookactive, resetview;
|
|
static vec3_t saved_look_offset;
|
|
vec3_t camera_offset;
|
|
|
|
target_angles = cg.refdefViewAngles;
|
|
target_position = cg.refdef.vieworg;
|
|
|
|
// see if angles are absolute
|
|
if (cg.predicted_player_state.camera_flags & CF_CAMERA_ANGLES_ABSOLUTE) {
|
|
VectorClear(target_angles);
|
|
}
|
|
|
|
// see if we need to ignore yaw
|
|
if (cg.predicted_player_state.camera_flags & CF_CAMERA_ANGLES_IGNORE_YAW) {
|
|
target_angles[YAW] = 0;
|
|
}
|
|
|
|
// see if we need to ignore pitch
|
|
if (cg.predicted_player_state.camera_flags & CF_CAMERA_ANGLES_IGNORE_PITCH) {
|
|
target_angles[PITCH] = 0;
|
|
}
|
|
|
|
// offset the current angles by the camera offset
|
|
VectorSubtract(target_angles, cg.predicted_player_state.camera_offset, target_angles);
|
|
|
|
// Get the position of the camera after any needed rotation
|
|
look_offset = cgi.get_camera_offset(&lookactive, &resetview);
|
|
|
|
if ((!resetview) && ((cg.predicted_player_state.camera_flags & CF_CAMERA_ANGLES_ALLOWOFFSET) || (lookactive))) {
|
|
VectorSubtract(look_offset, saved_look_offset, camera_offset);
|
|
VectorAdd(target_angles, camera_offset, target_angles);
|
|
if (target_angles[PITCH] > 90) {
|
|
target_angles[PITCH] = 90;
|
|
} else if (target_angles[PITCH] < -90) {
|
|
target_angles[PITCH] = -90;
|
|
}
|
|
} else {
|
|
VectorCopy(look_offset, saved_look_offset);
|
|
}
|
|
|
|
target_angles[YAW] = AngleNormalize360(target_angles[YAW]);
|
|
target_angles[PITCH] = AngleNormalize180(target_angles[PITCH]);
|
|
|
|
// Move reference point up
|
|
|
|
target_position[2] += cg_cameraheight->value;
|
|
|
|
VectorCopy(target_position, original_camera_position);
|
|
|
|
// Move camera back from reference point
|
|
|
|
AngleVectors(target_angles, forward, NULL, NULL);
|
|
|
|
VectorMA(target_position, -cg_cameradist->value, forward, new_vieworg);
|
|
|
|
new_vieworg[2] += cg_cameraverticaldisplacement->value;
|
|
|
|
// Create a bounding box for our camera
|
|
|
|
min[0] = -5;
|
|
min[1] = -5;
|
|
min[2] = -5;
|
|
|
|
max[0] = 5;
|
|
max[1] = 5;
|
|
max[2] = 5;
|
|
|
|
// Make sure camera does not collide with anything
|
|
CG_Trace(&trace, cg.playerHeadPos, min, max, new_vieworg, 0, MASK_CAMERASOLID, qfalse, qtrue, "ThirdPersonTrace 1");
|
|
|
|
VectorCopy(trace.endpos, target_position);
|
|
|
|
// calculate distance from end position to head position
|
|
VectorSubtract(target_position, cg.playerHeadPos, delta);
|
|
// kill any negative z difference in delta
|
|
if (delta[2] < CAMERA_MINIMUM_DISTANCE) {
|
|
delta[2] = 0;
|
|
}
|
|
if (VectorLength(delta) < CAMERA_MINIMUM_DISTANCE) {
|
|
VectorNormalize(delta);
|
|
/*
|
|
// see if we are going straight up
|
|
if ( ( delta[ 2 ] > 0.75 ) && ( height > 0.85f * cg.predicted_player_state.viewheight ) )
|
|
{
|
|
// we just need to lower our start position slightly, since we are on top of the player
|
|
new_vieworg[ 2 ] -= 16;
|
|
CG_Trace(&trace, cg.playerHeadPos, min, max, new_vieworg, 0, MASK_CAMERASOLID, qfalse, true, "ThirdPersonTrace 2" );
|
|
VectorCopy(trace.endpos, target_position);
|
|
}
|
|
else
|
|
*/
|
|
{
|
|
// we are probably up against the wall so we want the camera to pitch up on top of the player
|
|
// save off the original angles
|
|
VectorCopy(target_angles, original_angles);
|
|
// start cranking up the target angles, pitch until we are the correct distance away from the player
|
|
while (target_angles[PITCH] < 90) {
|
|
target_angles[PITCH] += 2;
|
|
|
|
AngleVectors(target_angles, forward, NULL, NULL);
|
|
|
|
VectorMA(original_camera_position, -cg_cameradist->value, forward, new_vieworg);
|
|
|
|
new_vieworg[2] += cg_cameraverticaldisplacement->value;
|
|
|
|
CG_Trace(
|
|
&trace,
|
|
cg.playerHeadPos,
|
|
min,
|
|
max,
|
|
new_vieworg,
|
|
0,
|
|
MASK_CAMERASOLID,
|
|
qfalse,
|
|
qtrue,
|
|
"ThirdPersonTrace 3"
|
|
);
|
|
|
|
VectorCopy(trace.endpos, target_position);
|
|
|
|
// calculate distance from end position to head position
|
|
VectorSubtract(target_position, cg.playerHeadPos, delta);
|
|
// kill any negative z difference in delta
|
|
if (delta[2] < 0) {
|
|
delta[2] = 0;
|
|
}
|
|
if (VectorLength(delta) >= CAMERA_MINIMUM_DISTANCE) {
|
|
target_angles[PITCH] = (0.25f * target_angles[PITCH]) + (0.75f * original_angles[PITCH]);
|
|
// set the pitch to be that of the angle we are currently looking
|
|
//target_angles[ PITCH ] = original_angles[ PITCH ];
|
|
break;
|
|
}
|
|
}
|
|
if (target_angles[PITCH] > 90) {
|
|
// if we failed, go with the original angles
|
|
target_angles[PITCH] = original_angles[PITCH];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_OffsetFirstPersonView
|
|
|
|
===============
|
|
*/
|
|
void CG_OffsetFirstPersonView(refEntity_t *pREnt, qboolean bUseWorldPosition)
|
|
{
|
|
float *origin;
|
|
centity_t *pCent;
|
|
dtiki_t *tiki;
|
|
int iTag;
|
|
int i;
|
|
int iMask;
|
|
vec3_t vDelta;
|
|
float mat[3][3];
|
|
vec3_t vOldOrigin;
|
|
vec3_t vStart, vEnd, vMins, vMaxs;
|
|
vec3_t vVelocity;
|
|
trace_t trace;
|
|
|
|
VectorSet(vMins, -6, -6, -6);
|
|
VectorSet(vMaxs, 6, 6, 6);
|
|
|
|
//
|
|
//
|
|
//
|
|
//
|
|
origin = cg.refdef.vieworg;
|
|
|
|
pCent = &cg_entities[cg.predicted_player_state.clientNum];
|
|
|
|
tiki = cgi.R_Model_GetHandle(cgs.model_draw[pCent->currentState.modelindex]);
|
|
iTag = cgi.Tag_NumForName(tiki, "eyes bone");
|
|
if (iTag != -1) {
|
|
if (bUseWorldPosition) {
|
|
orientation_t oHead;
|
|
float mat3[3][3];
|
|
vec3_t vHeadAng, vDelta;
|
|
|
|
VectorCopy(pCent->lerpOrigin, origin);
|
|
AxisCopy(pREnt->axis, mat);
|
|
oHead = cgi.TIKI_Orientation(pREnt, iTag);
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
VectorMA(origin, oHead.origin[i], mat[i], origin);
|
|
}
|
|
|
|
R_ConcatRotations(oHead.axis, mat, mat3);
|
|
MatrixToEulerAngles(mat3, vHeadAng);
|
|
AnglesSubtract(vHeadAng, cg.refdefViewAngles, vDelta);
|
|
VectorMA(cg.refdefViewAngles, cg.fEyeOffsetFrac, vDelta, cg.refdefViewAngles);
|
|
VectorCopy(vHeadAng, cg.refdefViewAngles);
|
|
} else {
|
|
orientation_t oHead;
|
|
vec3_t vHeadAng;
|
|
|
|
VectorCopy(pCent->lerpOrigin, origin);
|
|
AxisCopy(pREnt->axis, mat);
|
|
MatrixToEulerAngles(mat, vHeadAng);
|
|
oHead = cgi.TIKI_Orientation(pREnt, iTag);
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
VectorMA(origin, oHead.origin[i], mat[i], origin);
|
|
}
|
|
|
|
cg.refdefViewAngles[2] += cg.predicted_player_state.fLeanAngle * 0.3;
|
|
}
|
|
} else {
|
|
cgi.DPrintf("CG_OffsetFirstPersonView warning: Couldn't find 'eyes bone' for player\n");
|
|
}
|
|
|
|
VectorCopy(origin, vOldOrigin);
|
|
|
|
if (!cg.predicted_player_state.walking || (!(cg.predicted_player_state.pm_flags & PMF_FROZEN) && !(cg.predicted_player_state.pm_flags & PMF_NO_MOVE))) {
|
|
VectorCopy(cg.predicted_player_state.velocity, vVelocity);
|
|
} else {
|
|
//
|
|
// Added in OPM
|
|
// When frozen, there must be no movement at all
|
|
VectorClear(vVelocity);
|
|
}
|
|
|
|
if (bUseWorldPosition) {
|
|
iMask = MASK_VIEWSOLID;
|
|
} else {
|
|
float fTargHeight;
|
|
float fHeightDelta, fHeightChange;
|
|
float fPhase, fVel;
|
|
vec3_t vDelta;
|
|
vec3_t vPivotPoint;
|
|
vec3_t vForward, vLeft;
|
|
|
|
origin[0] = cg.predicted_player_state.origin[0];
|
|
origin[1] = cg.predicted_player_state.origin[1];
|
|
fTargHeight = cg.predicted_player_state.origin[2] + cg.predicted_player_state.viewheight;
|
|
fHeightDelta = fTargHeight - cg.fCurrentViewHeight;
|
|
|
|
if (fabs(fHeightDelta) < 0.1 || !cg.fCurrentViewHeight) {
|
|
cg.fCurrentViewHeight = fTargHeight;
|
|
} else {
|
|
if (fHeightDelta > 32.f) {
|
|
fHeightDelta = 32.f;
|
|
cg.fCurrentViewHeight = fTargHeight - 32.0;
|
|
} else if (fHeightDelta < -32.f) {
|
|
fHeightDelta = -32.f;
|
|
cg.fCurrentViewHeight = fTargHeight + 32.0;
|
|
}
|
|
|
|
fHeightChange = cg.frametime / 1000.0 * fHeightDelta * 12.5;
|
|
if (!cg.predicted_player_state.walking) {
|
|
fHeightChange += fHeightChange;
|
|
}
|
|
|
|
if (fabs(fHeightDelta) < fabs(fHeightChange)) {
|
|
fHeightChange = fHeightDelta;
|
|
}
|
|
|
|
cg.fCurrentViewHeight += fHeightChange;
|
|
}
|
|
|
|
origin[2] = cg.fCurrentViewHeight;
|
|
vPivotPoint[0] = cg.refdefViewAngles[0];
|
|
vPivotPoint[1] = cg.refdefViewAngles[1];
|
|
vPivotPoint[2] = 0.0;
|
|
AngleVectorsLeft(vPivotPoint, vForward, vLeft, NULL);
|
|
|
|
VectorCopy(origin, vStart);
|
|
|
|
if (cg.predicted_player_state.pm_type != PM_CLIMBWALL) {
|
|
if (cg.refdefViewAngles[0] > 0.0) {
|
|
vStart[2] -= (cg.fCurrentViewHeight - cg.predicted_player_state.origin[2]) * 0.4;
|
|
} else {
|
|
vStart[2] -= (cg.fCurrentViewHeight - cg.predicted_player_state.origin[2]) * 0.2;
|
|
}
|
|
} else {
|
|
vStart[2] -= (cg.fCurrentViewHeight - cg.predicted_player_state.origin[2]) * 0.15;
|
|
}
|
|
|
|
VectorSubtract(origin, vStart, vDelta);
|
|
RotatePointAroundVector(vEnd, vLeft, vDelta, cg.refdefViewAngles[0] * 0.4);
|
|
VectorAdd(vStart, vEnd, origin);
|
|
|
|
if (cg.predicted_player_state.fLeanAngle) {
|
|
VectorCopy(origin, vStart);
|
|
vStart[2] -= 28.7f;
|
|
|
|
VectorSubtract(origin, vStart, vDelta);
|
|
RotatePointAroundVector(vEnd, vForward, vDelta, cg.predicted_player_state.fLeanAngle);
|
|
VectorAdd(vStart, vEnd, origin);
|
|
}
|
|
|
|
if (cg.predicted_player_state.walking) {
|
|
fVel = VectorLength(vVelocity);
|
|
fPhase = fVel * 0.0015 + 0.9;
|
|
cg.fCurrentViewBobPhase += (cg.frametime / 1000.0 + cg.frametime / 1000.0) * M_PI * fPhase;
|
|
|
|
if (cg.fCurrentViewBobAmp) {
|
|
cg.fCurrentViewBobAmp = fVel;
|
|
} else {
|
|
cg.fCurrentViewBobAmp = fVel * 0.5;
|
|
}
|
|
|
|
if (cg.predicted_player_state.fLeanAngle != 0.0) {
|
|
cg.fCurrentViewBobAmp *= 0.75;
|
|
}
|
|
|
|
cg.fCurrentViewBobAmp *= (1.0 - fabs(cg.refdefViewAngles[0]) * (1.0 / 90.0) * 0.5) * 0.5;
|
|
} else if (cg.fCurrentViewBobAmp > 0.0) {
|
|
cg.fCurrentViewBobAmp -=
|
|
(cg.frametime / 1000.0 * cg.fCurrentViewBobAmp) + (cg.frametime / 1000.0 * cg.fCurrentViewBobAmp);
|
|
|
|
if (cg.fCurrentViewBobAmp < 0.1) {
|
|
cg.fCurrentViewBobAmp = 0.0;
|
|
}
|
|
}
|
|
|
|
if (cg.fCurrentViewBobAmp > 0.0) {
|
|
fPhase = sin(cg.fCurrentViewBobPhase) * cg.fCurrentViewBobAmp * 0.03;
|
|
|
|
if (fPhase > 16.0) {
|
|
fPhase = 16.0;
|
|
} else if (fPhase < -16.0) {
|
|
fPhase = -16.0;
|
|
}
|
|
|
|
VectorMA(origin, fPhase, vLeft, origin);
|
|
|
|
fPhase = sin(cg.fCurrentViewBobPhase - 0.94);
|
|
fPhase = (fabs(fPhase) - 0.5) * cg.fCurrentViewBobAmp * 0.06;
|
|
|
|
if (fPhase > 16.0) {
|
|
fPhase = 16.0;
|
|
} else if (fPhase < -16.0) {
|
|
fPhase = -16.0;
|
|
}
|
|
|
|
origin[2] += fPhase;
|
|
}
|
|
|
|
iMask = MASK_PLAYERSOLID;
|
|
}
|
|
|
|
vStart[0] = cg.predicted_player_state.origin[0];
|
|
vStart[1] = cg.predicted_player_state.origin[1];
|
|
vStart[2] = cg.predicted_player_state.origin[2] + cg.predicted_player_state.viewheight;
|
|
vEnd[0] = cg.predicted_player_state.origin[0];
|
|
vEnd[1] = cg.predicted_player_state.origin[1];
|
|
vEnd[2] = origin[2];
|
|
|
|
CG_Trace(
|
|
&trace, vStart, vMins, vMaxs, vEnd, cg.snap->ps.clientNum, iMask, qfalse, qtrue, "FirstPerson Height Check"
|
|
);
|
|
|
|
VectorCopy(trace.endpos, vStart);
|
|
vEnd[0] = origin[0];
|
|
vEnd[1] = origin[1];
|
|
vEnd[2] = trace.endpos[2];
|
|
CG_Trace(&trace, vStart, vMins, vMaxs, vEnd, cg.snap->ps.clientNum, iMask, 0, 1, "FirstPerson Lateral Check");
|
|
|
|
VectorCopy(trace.endpos, origin);
|
|
VectorSubtract(origin, vOldOrigin, vDelta);
|
|
VectorAdd(pREnt->origin, vDelta, pREnt->origin);
|
|
|
|
if (!bUseWorldPosition) {
|
|
VectorCopy(cg.refdefViewAngles, vDelta);
|
|
vDelta[0] *= 0.5;
|
|
vDelta[2] *= 0.75;
|
|
|
|
AngleVectorsLeft(vDelta, mat[0], mat[1], mat[2]);
|
|
|
|
CG_CalcViewModelMovement(
|
|
cg.fCurrentViewBobPhase, cg.fCurrentViewBobAmp, vVelocity, vDelta
|
|
);
|
|
|
|
VectorMA(pREnt->origin, vDelta[0], mat[0], pREnt->origin);
|
|
VectorMA(pREnt->origin, vDelta[1], mat[1], pREnt->origin);
|
|
VectorMA(pREnt->origin, vDelta[2], mat[2], pREnt->origin);
|
|
}
|
|
|
|
VectorCopy(origin, cg.playerHeadPos);
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CG_CalcFov
|
|
|
|
Fixed fov at intermissions, otherwise account for fov variable and zooms.
|
|
====================
|
|
*/
|
|
#define WAVE_AMPLITUDE 1
|
|
#define WAVE_FREQUENCY 0.4
|
|
|
|
static int CG_CalcFov(void)
|
|
{
|
|
float x;
|
|
float phase;
|
|
float v;
|
|
int contents;
|
|
float fov_x, fov_y;
|
|
int inwater;
|
|
float fov_ratio;
|
|
|
|
fov_ratio = (float)cg.refdef.width / (float)cg.refdef.height * (3.0 / 4.0);
|
|
if (fov_ratio == 1) {
|
|
fov_x = cg.camera_fov;
|
|
} else {
|
|
fov_x = RAD2DEG(atan(tan(DEG2RAD(cg.camera_fov / 2.0)) * fov_ratio)) * 2.0;
|
|
}
|
|
|
|
x = cg.refdef.width / tan(fov_x / 360 * M_PI);
|
|
fov_y = atan2(cg.refdef.height, x);
|
|
fov_y = fov_y * 360 / M_PI;
|
|
|
|
// warp if underwater
|
|
contents = CG_PointContents(cg.refdef.vieworg, -1);
|
|
if (contents & (CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA)) {
|
|
phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2;
|
|
v = WAVE_AMPLITUDE * sin(phase);
|
|
fov_x += v;
|
|
fov_y -= v;
|
|
inwater = qtrue;
|
|
} else {
|
|
inwater = qfalse;
|
|
}
|
|
|
|
// set it
|
|
cg.refdef.fov_x = fov_x;
|
|
cg.refdef.fov_y = fov_y;
|
|
cg.fRefFovXCos = cos(fov_x / 114.0f);
|
|
cg.fRefFovXSin = sin(fov_x / 114.0f);
|
|
cg.fRefFovYCos = cos(fov_y / 114.0f);
|
|
cg.fRefFovYSin = sin(fov_y / 114.0f);
|
|
cg.zoomSensitivity = cg.refdef.fov_y / 75.0;
|
|
return inwater;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_SetupFog
|
|
|
|
Prepares fog values for rendering
|
|
===============
|
|
*/
|
|
void CG_SetupFog() {
|
|
cg.refdef.farplane_distance = cg.farplane_distance;
|
|
cg.refdef.farplane_bias = cg.farplane_bias;
|
|
cg.refdef.farplane_color[0] = cg.farplane_color[0];
|
|
cg.refdef.farplane_color[1] = cg.farplane_color[1];
|
|
cg.refdef.farplane_color[2] = cg.farplane_color[2];
|
|
cg.refdef.farplane_cull = cg.farplane_cull;
|
|
cg.refdef.skybox_farplane = cg.skyboxFarplane;
|
|
cg.refdef.renderTerrain = cg.renderTerrain;
|
|
cg.refdef.farclipOverride = cg.farclipOverride;
|
|
cg.refdef.farplaneColorOverride[0] = cg.farplaneColorOverride[0];
|
|
cg.refdef.farplaneColorOverride[1] = cg.farplaneColorOverride[1];
|
|
cg.refdef.farplaneColorOverride[2] = cg.farplaneColorOverride[2];
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_SetupPortalSky
|
|
|
|
Sets portalsky values for rendering
|
|
===============
|
|
*/
|
|
void CG_SetupPortalSky() {
|
|
cg.refdef.sky_alpha = cg.sky_alpha;
|
|
cg.refdef.sky_portal = cg.sky_portal;
|
|
VectorCopy(cg.sky_axis[0], cg.refdef.sky_axis[0]);
|
|
VectorCopy(cg.sky_axis[1], cg.refdef.sky_axis[1]);
|
|
VectorCopy(cg.sky_axis[2], cg.refdef.sky_axis[2]);
|
|
VectorMA(cg.sky_origin, cg.skyboxSpeed, cg.refdef.vieworg, cg.refdef.sky_origin);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_CalcViewValues
|
|
|
|
Sets cg.refdef view values
|
|
===============
|
|
*/
|
|
static int CG_CalcViewValues(void)
|
|
{
|
|
playerState_t *ps;
|
|
float SoundAngles[3];
|
|
|
|
memset(&cg.refdef, 0, sizeof(cg.refdef));
|
|
|
|
// calculate size of 3D view
|
|
CG_CalcVrect();
|
|
CG_SetupFog();
|
|
|
|
ps = &cg.predicted_player_state;
|
|
|
|
VectorCopy(ps->origin, cg.refdef.vieworg);
|
|
VectorCopy(ps->viewangles, cg.refdefViewAngles);
|
|
|
|
if (cg.snap->ps.stats[STAT_HEALTH] > 0) {
|
|
VectorSubtract(cg.refdefViewAngles, cg.predicted_player_state.damage_angles, cg.refdefViewAngles);
|
|
cg.refdefViewAngles[0] += cg.viewkick[0];
|
|
cg.refdefViewAngles[1] += cg.viewkick[1];
|
|
|
|
if (cg.viewkick[0] || cg.viewkick[1]) {
|
|
int i;
|
|
float fDecay;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
fDecay = cg.viewkick[i] * cg.viewkickRecenter;
|
|
if (fDecay > cg.viewkickMaxDecay) {
|
|
fDecay = cg.viewkickMaxDecay;
|
|
} else if (fDecay < -cg.viewkickMaxDecay) {
|
|
fDecay = -cg.viewkickMaxDecay;
|
|
}
|
|
|
|
if (fabs(fDecay) < cg.viewkickMinDecay) {
|
|
if (fDecay > 0.0) {
|
|
fDecay = cg.viewkickMinDecay;
|
|
} else {
|
|
fDecay = -cg.viewkickMinDecay;
|
|
}
|
|
}
|
|
|
|
if (cg.viewkick[i] > 0.0) {
|
|
cg.viewkick[i] -= fDecay * (float)cg.frametime / 1000.0;
|
|
if (cg.viewkick[i] < 0.0) {
|
|
cg.viewkick[i] = 0.0;
|
|
}
|
|
} else {
|
|
cg.viewkick[i] -= fDecay * (float)cg.frametime / 1000.0;
|
|
if (cg.viewkick[i] > 0.0) {
|
|
cg.viewkick[i] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME: fffx screen shake on win32 builds?
|
|
|
|
// add error decay
|
|
if (cg_errorDecay->value > 0) {
|
|
int t;
|
|
float f;
|
|
|
|
t = cg.time - cg.predictedErrorTime;
|
|
f = (cg_errorDecay->value - t) / cg_errorDecay->value;
|
|
if (f > 0 && f < 1) {
|
|
VectorMA(cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg);
|
|
} else {
|
|
cg.predictedErrorTime = 0;
|
|
}
|
|
}
|
|
|
|
// calculate position of player's head
|
|
cg.refdef.vieworg[2] += cg.predicted_player_state.viewheight;
|
|
// save off the position of the player's head
|
|
VectorCopy(cg.refdef.vieworg, cg.playerHeadPos);
|
|
|
|
// Set the aural position of the player
|
|
VectorCopy(cg.playerHeadPos, cg.SoundOrg);
|
|
|
|
// Set the aural axis of the player
|
|
VectorCopy(cg.refdefViewAngles, SoundAngles);
|
|
// yaw is purposely inverted because of the miles sound system
|
|
// Commented out in OPM
|
|
// Useless as SDL audio/AL is used
|
|
//SoundAngles[YAW] = -SoundAngles[YAW];
|
|
AnglesToAxis(SoundAngles, cg.SoundAxis);
|
|
|
|
// decide on third person view
|
|
cg.renderingThirdPerson = cg_3rd_person->integer;
|
|
|
|
if (cg.renderingThirdPerson) {
|
|
// back away from character
|
|
CG_OffsetThirdPersonView();
|
|
}
|
|
|
|
// if we are in a camera view, we take our audio cues directly from the camera
|
|
if (ps->pm_flags & PMF_CAMERA_VIEW) {
|
|
// Set the aural position to that of the camera
|
|
VectorCopy(cg.camera_origin, cg.refdef.vieworg);
|
|
|
|
// Set the aural axis to the camera's angles
|
|
VectorCopy(cg.camera_angles, cg.refdefViewAngles);
|
|
|
|
if (cg_protocol >= PROTOCOL_MOHTA_MIN && (ps->pm_flags & PMF_DAMAGE_ANGLES)) {
|
|
// Handle camera shake
|
|
VectorSubtract(cg.refdefViewAngles, cg.predicted_player_state.damage_angles, cg.refdefViewAngles);
|
|
}
|
|
|
|
if (ps->camera_posofs[0] || ps->camera_posofs[1] || ps->camera_posofs[2]) {
|
|
vec3_t vAxis[3], vOrg;
|
|
AnglesToAxis(cg.refdefViewAngles, vAxis);
|
|
MatrixTransformVector(ps->camera_posofs, vAxis, vOrg);
|
|
VectorAdd(cg.refdef.vieworg, vOrg, cg.refdef.vieworg);
|
|
}
|
|
|
|
// copy view values
|
|
VectorCopy(cg.refdef.vieworg, cg.currentViewPos);
|
|
VectorCopy(cg.refdefViewAngles, cg.currentViewAngles);
|
|
// since 2.0: also copy location data for sound
|
|
VectorCopy(cg.refdef.vieworg, cg.SoundOrg);
|
|
AnglesToAxis(cg.refdefViewAngles, cg.SoundAxis);
|
|
}
|
|
|
|
// position eye reletive to origin
|
|
AnglesToAxis(cg.refdefViewAngles, cg.refdef.viewaxis);
|
|
|
|
if (cg.hyperspace) {
|
|
cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE;
|
|
}
|
|
|
|
// field of view
|
|
return CG_CalcFov();
|
|
}
|
|
|
|
void CG_EyePosition(vec3_t *o_vPos)
|
|
{
|
|
(*o_vPos)[0] = cg.playerHeadPos[0];
|
|
(*o_vPos)[1] = cg.playerHeadPos[1];
|
|
(*o_vPos)[2] = cg.playerHeadPos[2];
|
|
}
|
|
|
|
void CG_EyeOffset(vec3_t *o_vOfs)
|
|
{
|
|
(*o_vOfs)[0] = cg.playerHeadPos[0] - cg.predicted_player_state.origin[0];
|
|
(*o_vOfs)[1] = cg.playerHeadPos[1] - cg.predicted_player_state.origin[1];
|
|
(*o_vOfs)[2] = cg.playerHeadPos[2] - cg.predicted_player_state.origin[2];
|
|
}
|
|
|
|
void CG_EyeAngles(vec3_t *o_vAngles)
|
|
{
|
|
(*o_vAngles)[0] = cg.refdefViewAngles[0];
|
|
(*o_vAngles)[1] = cg.refdefViewAngles[1];
|
|
(*o_vAngles)[2] = cg.refdefViewAngles[2];
|
|
}
|
|
|
|
float CG_SensitivityScale()
|
|
{
|
|
return cg.zoomSensitivity;
|
|
}
|
|
|
|
void CG_AddLightShow()
|
|
{
|
|
int i;
|
|
float fSlopeY, fSlopeZ;
|
|
float x, y, z;
|
|
vec3_t vOrg;
|
|
float r, g, b;
|
|
float fMax;
|
|
|
|
fSlopeY = tan(cg.refdef.fov_x * 0.5);
|
|
fSlopeZ = tan(cg.refdef.fov_y * 0.5);
|
|
|
|
for (i = 0; i < cg_acidtrip->integer; i++) {
|
|
x = pow(random(), 1.0 / 3.0) * 2048.0;
|
|
y = crandom() * x * fSlopeY;
|
|
z = crandom() * x * fSlopeZ;
|
|
|
|
VectorCopy(cg.refdef.vieworg, vOrg);
|
|
VectorMA(vOrg, x, cg.refdef.viewaxis[0], vOrg);
|
|
VectorMA(vOrg, y, cg.refdef.viewaxis[1], vOrg);
|
|
VectorMA(vOrg, z, cg.refdef.viewaxis[2], vOrg);
|
|
|
|
r = random();
|
|
g = random();
|
|
b = random();
|
|
|
|
fMax = Q_max(r, Q_max(g, b));
|
|
r /= fMax;
|
|
g /= fMax;
|
|
b /= fMax;
|
|
|
|
cgi.R_AddLightToScene(vOrg, (rand() & 0x1FF) + 0x80, r, g, b, 0);
|
|
}
|
|
}
|
|
|
|
qboolean CG_FrustumCullSphere(const vec3_t vPos, float fRadius) {
|
|
vec3_t delta;
|
|
float fDotFwd, fDotSide, fDotUp;
|
|
|
|
VectorSubtract(vPos, cg.refdef.vieworg, delta);
|
|
|
|
fDotFwd = DotProduct(delta, cg.refdef.viewaxis[0]);
|
|
if (-fRadius >= fDotFwd) {
|
|
return qtrue;
|
|
}
|
|
|
|
if (cg.refdef.farplane_distance && cg.refdef.farplane_distance + fRadius <= fDotFwd) {
|
|
return qtrue;
|
|
}
|
|
|
|
fDotSide = DotProduct(delta, cg.refdef.viewaxis[1]);
|
|
if (fDotSide < 1.f) {
|
|
fDotSide = -fDotSide;
|
|
}
|
|
|
|
if (fDotSide * cg.fRefFovXCos - fDotFwd * cg.fRefFovXSin >= fRadius) {
|
|
return qtrue;
|
|
}
|
|
|
|
fDotUp = DotProduct(delta, cg.refdef.viewaxis[2]);
|
|
if (fDotUp < 0.f) {
|
|
fDotUp = -fDotUp;
|
|
}
|
|
|
|
if (fDotUp * cg.fRefFovYCos - fDotFwd * cg.fRefFovYSin >= fRadius) {
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
//=========================================================================
|
|
|
|
/*
|
|
=================
|
|
CG_DrawActiveFrame
|
|
|
|
Generates and draws a game scene and status information at the given time.
|
|
=================
|
|
*/
|
|
void CG_DrawActiveFrame(int serverTime, int frameTime, stereoFrame_t stereoView, qboolean demoPlayback)
|
|
{
|
|
cg.time = serverTime;
|
|
cg.frametime = frameTime;
|
|
cg.demoPlayback = demoPlayback;
|
|
|
|
// any looped sounds will be respecified as entities
|
|
// are added to the render list
|
|
cgi.S_ClearLoopingSounds();
|
|
|
|
// clear all the render lists
|
|
cgi.R_ClearScene();
|
|
|
|
// set up cg.snap and possibly cg.nextSnap
|
|
CG_ProcessSnapshots();
|
|
|
|
// if we haven't received any snapshots yet, all
|
|
// we can draw is the information screen
|
|
if (!cg.snap || (cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE)) {
|
|
return;
|
|
}
|
|
|
|
// this counter will be bumped for every valid scene we generate
|
|
cg.clientFrame++;
|
|
|
|
// set cg.frameInterpolation
|
|
if (cg.nextSnap && r_lerpmodels->integer) {
|
|
int delta;
|
|
|
|
delta = (cg.nextSnap->serverTime - cg.snap->serverTime);
|
|
if (delta == 0) {
|
|
cg.frameInterpolation = 0;
|
|
} else {
|
|
cg.frameInterpolation = (float)(cg.time - cg.snap->serverTime) / delta;
|
|
}
|
|
} else {
|
|
cg.frameInterpolation = 0; // actually, it should never be used, because
|
|
// no entities should be marked as interpolating
|
|
}
|
|
|
|
//
|
|
// Added in OPM
|
|
// Clamp the fov to avoid artifacts
|
|
if (cg_fov->value < 65) {
|
|
cgi.Cvar_Set("cg_fov", "65");
|
|
} else if (cg_fov->value > 120) {
|
|
cgi.Cvar_Set("cg_fov", "120");
|
|
}
|
|
|
|
// update cg.predicted_player_state
|
|
CG_PredictPlayerState();
|
|
|
|
// build cg.refdef
|
|
CG_CalcViewValues();
|
|
|
|
// display the intermission
|
|
if (cg.snap->ps.pm_flags & PMF_INTERMISSION) {
|
|
if (cgs.gametype != GT_SINGLE_PLAYER) {
|
|
CG_ScoresDown_f();
|
|
} else if (cg.bIntermissionDisplay) {
|
|
if (cg.nextSnap) {
|
|
if (cg_protocol >= PROTOCOL_MOHTA_MIN) {
|
|
cvar_t* pMission = cgi.Cvar_Get("g_mission", "", CVAR_ARCHIVE);
|
|
|
|
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
|
|
switch (pMission->integer)
|
|
{
|
|
default:
|
|
case 0:
|
|
cgi.UI_ShowMenu("mission_success_1", 0);
|
|
cgi.Cvar_Set("g_t2l1", "1");
|
|
break;
|
|
case 2:
|
|
cgi.UI_ShowMenu("mission_success_2", 0);
|
|
cgi.Cvar_Set("g_t3l1", "1");
|
|
break;
|
|
case 3:
|
|
cgi.UI_ShowMenu("mission_success_3", 0);
|
|
break;
|
|
}
|
|
} else {
|
|
switch (pMission->integer)
|
|
{
|
|
default:
|
|
case 0:
|
|
cgi.UI_ShowMenu("mission_failed_1", 0);
|
|
break;
|
|
case 2:
|
|
cgi.UI_ShowMenu("mission_failed_2", 0);
|
|
break;
|
|
case 3:
|
|
cgi.UI_ShowMenu("mission_failed_3", 0);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
|
|
cgi.UI_ShowMenu("StatsScreen_Success", qfalse);
|
|
} else {
|
|
cgi.UI_ShowMenu("StatsScreen_Failed", qfalse);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
cgi.SendClientCommand("stats");
|
|
}
|
|
|
|
cg.bIntermissionDisplay = qtrue;
|
|
} else if (cg.bIntermissionDisplay) {
|
|
if (cgs.gametype != GT_SINGLE_PLAYER) {
|
|
CG_ScoresUp_f();
|
|
} else {
|
|
if (cg_protocol >= PROTOCOL_MOHTA_MIN) {
|
|
cvar_t* pMission = cgi.Cvar_Get("g_mission", "", CVAR_ARCHIVE);
|
|
|
|
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
|
|
switch (pMission->integer)
|
|
{
|
|
default:
|
|
case 0:
|
|
cgi.UI_HideMenu("mission_success_1", qtrue);
|
|
break;
|
|
case 2:
|
|
cgi.UI_HideMenu("mission_success_2", qtrue);
|
|
break;
|
|
case 3:
|
|
cgi.UI_HideMenu("mission_success_3", qtrue);
|
|
break;
|
|
}
|
|
} else {
|
|
switch (pMission->integer)
|
|
{
|
|
default:
|
|
case 0:
|
|
cgi.UI_HideMenu("mission_failed_1", qtrue);
|
|
break;
|
|
case 2:
|
|
cgi.UI_HideMenu("mission_failed_2", qtrue);
|
|
break;
|
|
case 3:
|
|
cgi.UI_HideMenu("mission_failed_3", qtrue);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
|
|
cgi.UI_HideMenu("StatsScreen_Success", qtrue);
|
|
} else {
|
|
cgi.UI_HideMenu("StatsScreen_Failed", qtrue);
|
|
}
|
|
}
|
|
}
|
|
|
|
cg.bIntermissionDisplay = qfalse;
|
|
}
|
|
|
|
// Added in OPM
|
|
CG_ProcessPlayerModel();
|
|
|
|
// build the render lists
|
|
if (!cg.hyperspace) {
|
|
CG_AddPacketEntities(); // after calcViewValues, so predicted player state is correct
|
|
CG_AddMarks();
|
|
}
|
|
|
|
// finish up the rest of the refdef
|
|
CG_SetupPortalSky();
|
|
|
|
cg.refdef.time = cg.time;
|
|
memcpy(cg.refdef.areamask, cg.snap->areamask, sizeof(cg.refdef.areamask));
|
|
|
|
// update audio positions
|
|
cgi.S_Respatialize(cg.snap->ps.clientNum, cg.SoundOrg, cg.SoundAxis);
|
|
|
|
// make sure the lagometerSample and frame timing isn't done twice when in stereo
|
|
if (stereoView != STEREO_RIGHT) {
|
|
CG_AddLagometerFrameInfo();
|
|
}
|
|
|
|
CG_UpdateTestEmitter();
|
|
CG_AddPendingEffects();
|
|
|
|
if (!cg_hidetempmodels->integer) {
|
|
CG_AddTempModels();
|
|
}
|
|
|
|
if (vss_draw->integer) {
|
|
CG_AddVSSSources();
|
|
}
|
|
|
|
CG_AddBulletTracers();
|
|
CG_AddBulletImpacts();
|
|
CG_AddBeams();
|
|
|
|
if (cg_acidtrip->integer) {
|
|
// lol disco
|
|
CG_AddLightShow();
|
|
}
|
|
|
|
// actually issue the rendering calls
|
|
CG_DrawActive(stereoView);
|
|
|
|
if (cg_stats->integer) {
|
|
cgi.Printf("cg.clientFrame:%i\n", cg.clientFrame);
|
|
}
|
|
}
|