2016-03-27 11:49:47 +02:00
|
|
|
/*
|
|
|
|
===========================================================================
|
2023-04-30 00:02:16 +02:00
|
|
|
Copyright (C) 2023 the OpenMoHAA team
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
This file is part of OpenMoHAA source code.
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
OpenMoHAA source code is free software; you can redistribute it
|
2016-03-27 11:49:47 +02:00
|
|
|
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.
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
OpenMoHAA source code is distributed in the hope that it will be
|
2016-03-27 11:49:47 +02:00
|
|
|
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
|
2023-04-30 00:02:16 +02:00
|
|
|
along with OpenMoHAA source code; if not, write to the Free Software
|
2016-03-27 11:49:47 +02:00
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
===========================================================================
|
|
|
|
*/
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
#include "cg_local.h"
|
2023-05-02 23:03:23 +02:00
|
|
|
#include "cg_parsemsg.h"
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
CG_CalcVrect
|
|
|
|
|
|
|
|
Sets the coordinates of the rendered window
|
|
|
|
=================
|
|
|
|
*/
|
2023-07-05 21:24:23 +02:00
|
|
|
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;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_OffsetThirdPersonView
|
|
|
|
|
|
|
|
===============
|
|
|
|
*/
|
2023-04-30 00:02:16 +02:00
|
|
|
#define CAMERA_MINIMUM_DISTANCE 40
|
2023-07-05 21:24:23 +02:00
|
|
|
|
|
|
|
static void CG_OffsetThirdPersonView(void)
|
2023-04-30 00:02:16 +02:00
|
|
|
{
|
2023-07-05 21:24:23 +02:00
|
|
|
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);
|
|
|
|
/*
|
2023-04-30 00:02:16 +02:00
|
|
|
// 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
|
|
|
|
*/
|
2023-07-05 21:24:23 +02:00
|
|
|
{
|
|
|
|
// 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;
|
|
|
|
}
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
if (target_angles[PITCH] > 90) {
|
|
|
|
// if we failed, go with the original angles
|
|
|
|
target_angles[PITCH] = original_angles[PITCH];
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_OffsetFirstPersonView
|
|
|
|
|
|
|
|
===============
|
|
|
|
*/
|
2023-07-05 21:24:23 +02:00
|
|
|
void CG_OffsetFirstPersonView(refEntity_t *pREnt, qboolean bUseWorldPosition)
|
2023-04-30 14:53:19 +02:00
|
|
|
{
|
2023-07-05 21:24:23 +02:00
|
|
|
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;
|
2024-11-10 16:07:55 +01:00
|
|
|
vec3_t vVelocity;
|
2023-07-05 21:24:23 +02:00
|
|
|
trace_t trace;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-05-02 23:03:23 +02:00
|
|
|
VectorSet(vMins, -6, -6, -6);
|
|
|
|
VectorSet(vMaxs, 6, 6, 6);
|
2023-07-05 21:24:23 +02:00
|
|
|
|
2023-05-02 23:03:23 +02:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
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");
|
2023-07-05 21:24:23 +02:00
|
|
|
if (iTag != -1) {
|
|
|
|
if (bUseWorldPosition) {
|
2023-05-02 23:03:23 +02:00
|
|
|
orientation_t oHead;
|
2023-07-05 21:24:23 +02:00
|
|
|
float mat3[3][3];
|
|
|
|
vec3_t vHeadAng, vDelta;
|
2023-05-02 23:03:23 +02:00
|
|
|
|
|
|
|
VectorCopy(pCent->lerpOrigin, origin);
|
|
|
|
AxisCopy(pREnt->axis, mat);
|
|
|
|
oHead = cgi.TIKI_Orientation(pREnt, iTag);
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
for (i = 0; i < 3; i++) {
|
2023-05-02 23:03:23 +02:00
|
|
|
VectorMA(origin, oHead.origin[i], mat[i], origin);
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
|
2023-05-02 23:03:23 +02:00
|
|
|
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);
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 23:03:23 +02:00
|
|
|
orientation_t oHead;
|
2023-07-05 21:24:23 +02:00
|
|
|
vec3_t vHeadAng;
|
2023-05-02 23:03:23 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-05-03 19:32:19 +02:00
|
|
|
cg.refdefViewAngles[2] += cg.predicted_player_state.fLeanAngle * 0.3;
|
2023-05-02 23:03:23 +02:00
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 23:03:23 +02:00
|
|
|
cgi.DPrintf("CG_OffsetFirstPersonView warning: Couldn't find 'eyes bone' for player\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorCopy(origin, vOldOrigin);
|
|
|
|
|
2024-12-12 22:52:28 +01:00
|
|
|
if (!cg.predicted_player_state.walking || (!(cg.predicted_player_state.pm_flags & PMF_FROZEN) && !(cg.predicted_player_state.pm_flags & PMF_NO_MOVE))) {
|
2024-11-10 16:07:55 +01:00
|
|
|
VectorCopy(cg.predicted_player_state.velocity, vVelocity);
|
|
|
|
} else {
|
|
|
|
//
|
|
|
|
// Added in OPM
|
|
|
|
// When frozen, there must be no movement at all
|
|
|
|
VectorClear(vVelocity);
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (bUseWorldPosition) {
|
2023-05-02 23:03:23 +02:00
|
|
|
iMask = MASK_VIEWSOLID;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
|
|
|
float fTargHeight;
|
|
|
|
float fHeightDelta, fHeightChange;
|
|
|
|
float fPhase, fVel;
|
2023-05-02 23:03:23 +02:00
|
|
|
vec3_t vDelta;
|
|
|
|
vec3_t vPivotPoint;
|
|
|
|
vec3_t vForward, vLeft;
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
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;
|
2023-05-02 23:03:23 +02:00
|
|
|
fHeightDelta = fTargHeight - cg.fCurrentViewHeight;
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (fabs(fHeightDelta) < 0.1 || !cg.fCurrentViewHeight) {
|
2023-05-02 23:03:23 +02:00
|
|
|
cg.fCurrentViewHeight = fTargHeight;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 23:03:23 +02:00
|
|
|
if (fHeightDelta > 32.f) {
|
2023-07-05 21:24:23 +02:00
|
|
|
fHeightDelta = 32.f;
|
2023-05-02 23:03:23 +02:00
|
|
|
cg.fCurrentViewHeight = fTargHeight - 32.0;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else if (fHeightDelta < -32.f) {
|
|
|
|
fHeightDelta = -32.f;
|
2023-05-02 23:03:23 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
origin[2] = cg.fCurrentViewHeight;
|
2023-05-02 23:03:23 +02:00
|
|
|
vPivotPoint[0] = cg.refdefViewAngles[0];
|
|
|
|
vPivotPoint[1] = cg.refdefViewAngles[1];
|
|
|
|
vPivotPoint[2] = 0.0;
|
|
|
|
AngleVectorsLeft(vPivotPoint, vForward, vLeft, NULL);
|
|
|
|
|
|
|
|
VectorCopy(origin, vStart);
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg.predicted_player_state.pm_type != PM_CLIMBWALL) {
|
2023-05-03 19:32:19 +02:00
|
|
|
if (cg.refdefViewAngles[0] > 0.0) {
|
2023-05-02 23:03:23 +02:00
|
|
|
vStart[2] -= (cg.fCurrentViewHeight - cg.predicted_player_state.origin[2]) * 0.4;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 23:03:23 +02:00
|
|
|
vStart[2] -= (cg.fCurrentViewHeight - cg.predicted_player_state.origin[2]) * 0.2;
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 23:03:23 +02:00
|
|
|
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);
|
2023-05-03 19:32:19 +02:00
|
|
|
vStart[2] -= 28.7f;
|
2023-05-02 23:03:23 +02:00
|
|
|
|
|
|
|
VectorSubtract(origin, vStart, vDelta);
|
|
|
|
RotatePointAroundVector(vEnd, vForward, vDelta, cg.predicted_player_state.fLeanAngle);
|
|
|
|
VectorAdd(vStart, vEnd, origin);
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg.predicted_player_state.walking) {
|
2024-11-10 16:07:55 +01:00
|
|
|
fVel = VectorLength(vVelocity);
|
2023-05-02 23:03:23 +02:00
|
|
|
fPhase = fVel * 0.0015 + 0.9;
|
2023-05-03 19:32:19 +02:00
|
|
|
cg.fCurrentViewBobPhase += (cg.frametime / 1000.0 + cg.frametime / 1000.0) * M_PI * fPhase;
|
2023-05-02 23:03:23 +02:00
|
|
|
|
|
|
|
if (cg.fCurrentViewBobAmp) {
|
|
|
|
cg.fCurrentViewBobAmp = fVel;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-02 23:03:23 +02:00
|
|
|
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;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else if (cg.fCurrentViewBobAmp > 0.0) {
|
|
|
|
cg.fCurrentViewBobAmp -=
|
|
|
|
(cg.frametime / 1000.0 * cg.fCurrentViewBobAmp) + (cg.frametime / 1000.0 * cg.fCurrentViewBobAmp);
|
2023-05-02 23:03:23 +02:00
|
|
|
|
2023-05-03 19:32:19 +02:00
|
|
|
if (cg.fCurrentViewBobAmp < 0.1) {
|
|
|
|
cg.fCurrentViewBobAmp = 0.0;
|
2023-05-02 23:03:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg.fCurrentViewBobAmp > 0.0) {
|
2023-05-02 23:03:23 +02:00
|
|
|
fPhase = sin(cg.fCurrentViewBobPhase) * cg.fCurrentViewBobAmp * 0.03;
|
2023-07-05 21:24:23 +02:00
|
|
|
|
2023-05-02 23:03:23 +02:00
|
|
|
if (fPhase > 16.0) {
|
|
|
|
fPhase = 16.0;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else if (fPhase < -16.0) {
|
2023-05-02 23:03:23 +02:00
|
|
|
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;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else if (fPhase < -16.0) {
|
2023-05-02 23:03:23 +02:00
|
|
|
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;
|
2023-07-05 21:24:23 +02:00
|
|
|
vEnd[0] = cg.predicted_player_state.origin[0];
|
|
|
|
vEnd[1] = cg.predicted_player_state.origin[1];
|
|
|
|
vEnd[2] = origin[2];
|
2023-05-02 23:03:23 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
CG_Trace(
|
|
|
|
&trace, vStart, vMins, vMaxs, vEnd, cg.snap->ps.clientNum, iMask, qfalse, qtrue, "FirstPerson Height Check"
|
|
|
|
);
|
2023-05-02 23:03:23 +02:00
|
|
|
|
|
|
|
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);
|
2023-05-03 19:32:19 +02:00
|
|
|
VectorAdd(pREnt->origin, vDelta, pREnt->origin);
|
2023-05-02 23:03:23 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!bUseWorldPosition) {
|
2023-05-02 23:03:23 +02:00
|
|
|
VectorCopy(cg.refdefViewAngles, vDelta);
|
|
|
|
vDelta[0] *= 0.5;
|
|
|
|
vDelta[2] *= 0.75;
|
|
|
|
|
|
|
|
AngleVectorsLeft(vDelta, mat[0], mat[1], mat[2]);
|
|
|
|
|
|
|
|
CG_CalcViewModelMovement(
|
2024-11-10 16:07:55 +01:00
|
|
|
cg.fCurrentViewBobPhase, cg.fCurrentViewBobAmp, vVelocity, vDelta
|
2023-05-02 23:03:23 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
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);
|
2023-04-30 14:53:19 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
====================
|
|
|
|
CG_CalcFov
|
|
|
|
|
|
|
|
Fixed fov at intermissions, otherwise account for fov variable and zooms.
|
|
|
|
====================
|
|
|
|
*/
|
2023-07-05 21:24:23 +02:00
|
|
|
#define WAVE_AMPLITUDE 1
|
|
|
|
#define WAVE_FREQUENCY 0.4
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
static int CG_CalcFov(void)
|
2023-04-30 14:53:19 +02:00
|
|
|
{
|
2023-07-05 21:24:23 +02:00
|
|
|
float x;
|
|
|
|
float phase;
|
|
|
|
float v;
|
|
|
|
int contents;
|
|
|
|
float fov_x, fov_y;
|
|
|
|
int inwater;
|
2025-01-02 15:52:45 +01:00
|
|
|
float fov_ratio;
|
2023-04-30 14:53:19 +02:00
|
|
|
|
2025-01-02 15:52:45 +01:00
|
|
|
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);
|
2023-04-30 14:53:19 +02:00
|
|
|
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;
|
2023-07-05 21:24:23 +02:00
|
|
|
v = WAVE_AMPLITUDE * sin(phase);
|
2023-04-30 14:53:19 +02:00
|
|
|
fov_x += v;
|
|
|
|
fov_y -= v;
|
|
|
|
inwater = qtrue;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-04-30 14:53:19 +02:00
|
|
|
inwater = qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set it
|
2023-07-05 21:24:23 +02:00
|
|
|
cg.refdef.fov_x = fov_x;
|
|
|
|
cg.refdef.fov_y = fov_y;
|
2023-07-15 20:40:57 +02:00
|
|
|
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);
|
2023-04-30 14:53:19 +02:00
|
|
|
cg.zoomSensitivity = cg.refdef.fov_y / 75.0;
|
|
|
|
return inwater;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-28 17:52:49 +02:00
|
|
|
/*
|
|
|
|
===============
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CG_CalcViewValues
|
|
|
|
|
|
|
|
Sets cg.refdef view values
|
|
|
|
===============
|
|
|
|
*/
|
2023-04-30 14:53:19 +02:00
|
|
|
static int CG_CalcViewValues(void)
|
|
|
|
{
|
2023-07-05 21:24:23 +02:00
|
|
|
playerState_t *ps;
|
|
|
|
float SoundAngles[3];
|
2023-04-30 14:53:19 +02:00
|
|
|
|
|
|
|
memset(&cg.refdef, 0, sizeof(cg.refdef));
|
|
|
|
|
|
|
|
// calculate size of 3D view
|
|
|
|
CG_CalcVrect();
|
2023-08-28 17:52:49 +02:00
|
|
|
CG_SetupFog();
|
2023-04-30 14:53:19 +02:00
|
|
|
|
|
|
|
ps = &cg.predicted_player_state;
|
|
|
|
|
|
|
|
VectorCopy(ps->origin, cg.refdef.vieworg);
|
|
|
|
VectorCopy(ps->viewangles, cg.refdefViewAngles);
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg.snap->ps.stats[STAT_HEALTH] > 0) {
|
2023-05-06 16:15:28 +02:00
|
|
|
VectorSubtract(cg.refdefViewAngles, cg.predicted_player_state.damage_angles, cg.refdefViewAngles);
|
|
|
|
cg.refdefViewAngles[0] += cg.viewkick[0];
|
|
|
|
cg.refdefViewAngles[1] += cg.viewkick[1];
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg.viewkick[0] || cg.viewkick[1]) {
|
|
|
|
int i;
|
2023-05-06 16:15:28 +02:00
|
|
|
float fDecay;
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
for (i = 0; i < 2; i++) {
|
2023-05-06 16:15:28 +02:00
|
|
|
fDecay = cg.viewkick[i] * cg.viewkickRecenter;
|
2023-07-05 21:24:23 +02:00
|
|
|
if (fDecay > cg.viewkickMaxDecay) {
|
2023-05-06 16:15:28 +02:00
|
|
|
fDecay = cg.viewkickMaxDecay;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else if (fDecay < -cg.viewkickMaxDecay) {
|
2023-05-06 16:15:28 +02:00
|
|
|
fDecay = -cg.viewkickMaxDecay;
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (fabs(fDecay) < cg.viewkickMinDecay) {
|
2023-05-06 17:50:41 +02:00
|
|
|
if (fDecay > 0.0) {
|
|
|
|
fDecay = cg.viewkickMinDecay;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-06 17:50:41 +02:00
|
|
|
fDecay = -cg.viewkickMinDecay;
|
|
|
|
}
|
2023-05-06 16:15:28 +02:00
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg.viewkick[i] > 0.0) {
|
2023-05-06 16:15:28 +02:00
|
|
|
cg.viewkick[i] -= fDecay * (float)cg.frametime / 1000.0;
|
|
|
|
if (cg.viewkick[i] < 0.0) {
|
|
|
|
cg.viewkick[i] = 0.0;
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-05-06 16:15:28 +02:00
|
|
|
cg.viewkick[i] -= fDecay * (float)cg.frametime / 1000.0;
|
|
|
|
if (cg.viewkick[i] > 0.0) {
|
|
|
|
cg.viewkick[i] = 0.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-04-30 14:53:19 +02:00
|
|
|
}
|
|
|
|
|
2023-05-06 16:15:28 +02:00
|
|
|
// FIXME: fffx screen shake on win32 builds?
|
|
|
|
|
2023-04-30 14:53:19 +02:00
|
|
|
// add error decay
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg_errorDecay->value > 0) {
|
|
|
|
int t;
|
|
|
|
float f;
|
2023-04-30 14:53:19 +02:00
|
|
|
|
|
|
|
t = cg.time - cg.predictedErrorTime;
|
|
|
|
f = (cg_errorDecay->value - t) / cg_errorDecay->value;
|
2023-07-05 21:24:23 +02:00
|
|
|
if (f > 0 && f < 1) {
|
2023-04-30 14:53:19 +02:00
|
|
|
VectorMA(cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg);
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-04-30 14:53:19 +02:00
|
|
|
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
|
2023-11-02 19:57:46 +01:00
|
|
|
// Commented out in OPM
|
|
|
|
// Useless as SDL audio/AL is used
|
|
|
|
//SoundAngles[YAW] = -SoundAngles[YAW];
|
2023-04-30 14:53:19 +02:00
|
|
|
AnglesToAxis(SoundAngles, cg.SoundAxis);
|
|
|
|
|
|
|
|
// decide on third person view
|
|
|
|
cg.renderingThirdPerson = cg_3rd_person->integer;
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg.renderingThirdPerson) {
|
2023-04-30 14:53:19 +02:00
|
|
|
// back away from character
|
|
|
|
CG_OffsetThirdPersonView();
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we are in a camera view, we take our audio cues directly from the camera
|
2023-07-05 21:24:23 +02:00
|
|
|
if (ps->pm_flags & PMF_CAMERA_VIEW) {
|
2023-04-30 14:53:19 +02:00
|
|
|
// 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);
|
|
|
|
|
2023-10-13 19:33:03 +02:00
|
|
|
if (cg_protocol >= PROTOCOL_MOHTA_MIN && (ps->pm_flags & PMF_DAMAGE_ANGLES)) {
|
|
|
|
// Handle camera shake
|
2023-08-28 18:35:47 +02:00
|
|
|
VectorSubtract(cg.refdefViewAngles, cg.predicted_player_state.damage_angles, cg.refdefViewAngles);
|
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (ps->camera_posofs[0] || ps->camera_posofs[1] || ps->camera_posofs[2]) {
|
2023-05-02 23:03:23 +02:00
|
|
|
vec3_t vAxis[3], vOrg;
|
2023-04-30 14:53:19 +02:00
|
|
|
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);
|
2023-08-28 18:35:47 +02:00
|
|
|
// since 2.0: also copy location data for sound
|
|
|
|
VectorCopy(cg.refdef.vieworg, cg.SoundOrg);
|
|
|
|
AnglesToAxis(cg.refdefViewAngles, cg.SoundAxis);
|
2023-04-30 14:53:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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();
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
void CG_EyePosition(vec3_t *o_vPos)
|
2023-05-01 00:10:23 +02:00
|
|
|
{
|
2023-05-01 15:14:17 +02:00
|
|
|
(*o_vPos)[0] = cg.playerHeadPos[0];
|
|
|
|
(*o_vPos)[1] = cg.playerHeadPos[1];
|
|
|
|
(*o_vPos)[2] = cg.playerHeadPos[2];
|
2023-05-01 00:10:23 +02:00
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
void CG_EyeOffset(vec3_t *o_vOfs)
|
2023-05-01 00:10:23 +02:00
|
|
|
{
|
2023-05-01 15:14:17 +02:00
|
|
|
(*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];
|
2023-05-01 00:10:23 +02:00
|
|
|
}
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
void CG_EyeAngles(vec3_t *o_vAngles)
|
2023-05-01 00:10:23 +02:00
|
|
|
{
|
2023-05-01 15:14:17 +02:00
|
|
|
(*o_vAngles)[0] = cg.refdefViewAngles[0];
|
|
|
|
(*o_vAngles)[1] = cg.refdefViewAngles[1];
|
|
|
|
(*o_vAngles)[2] = cg.refdefViewAngles[2];
|
2023-05-01 00:10:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
float CG_SensitivityScale()
|
|
|
|
{
|
2023-05-01 15:14:17 +02:00
|
|
|
return cg.zoomSensitivity;
|
2023-05-01 00:10:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void CG_AddLightShow()
|
|
|
|
{
|
2024-09-08 18:48:15 +02:00
|
|
|
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);
|
|
|
|
}
|
2023-05-01 00:10:23 +02:00
|
|
|
}
|
|
|
|
|
2023-07-15 20:18:20 +02:00
|
|
|
qboolean CG_FrustumCullSphere(const vec3_t vPos, float fRadius) {
|
2023-07-15 20:40:57 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-07-15 20:18:20 +02:00
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
//=========================================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
CG_DrawActiveFrame
|
|
|
|
|
|
|
|
Generates and draws a game scene and status information at the given time.
|
|
|
|
=================
|
|
|
|
*/
|
2023-07-05 21:24:23 +02:00
|
|
|
void CG_DrawActiveFrame(int serverTime, int frameTime, stereoFrame_t stereoView, qboolean demoPlayback)
|
2023-04-30 14:53:19 +02:00
|
|
|
{
|
2023-07-05 21:24:23 +02:00
|
|
|
cg.time = serverTime;
|
|
|
|
cg.frametime = frameTime;
|
2023-04-30 14:53:19 +02:00
|
|
|
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
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!cg.snap || (cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE)) {
|
2023-04-30 14:53:19 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this counter will be bumped for every valid scene we generate
|
|
|
|
cg.clientFrame++;
|
|
|
|
|
|
|
|
// set cg.frameInterpolation
|
|
|
|
if (cg.nextSnap && r_lerpmodels->integer) {
|
2023-07-05 21:24:23 +02:00
|
|
|
int delta;
|
2023-04-30 14:53:19 +02:00
|
|
|
|
|
|
|
delta = (cg.nextSnap->serverTime - cg.snap->serverTime);
|
|
|
|
if (delta == 0) {
|
|
|
|
cg.frameInterpolation = 0;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-04-30 14:53:19 +02:00
|
|
|
cg.frameInterpolation = (float)(cg.time - cg.snap->serverTime) / delta;
|
|
|
|
}
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
|
|
|
cg.frameInterpolation = 0; // actually, it should never be used, because
|
2023-04-30 14:53:19 +02:00
|
|
|
// no entities should be marked as interpolating
|
|
|
|
}
|
|
|
|
|
2024-12-23 22:53:31 +01:00
|
|
|
//
|
|
|
|
// Added in OPM
|
|
|
|
// Clamp the fov to avoid artifacts
|
|
|
|
if (cg_fov->value < 65) {
|
|
|
|
cgi.Cvar_Set("cg_fov", "65");
|
2024-12-23 22:57:26 +01:00
|
|
|
} else if (cg_fov->value > 120) {
|
2024-12-23 22:53:31 +01:00
|
|
|
cgi.Cvar_Set("cg_fov", "120");
|
|
|
|
}
|
|
|
|
|
2023-04-30 14:53:19 +02:00
|
|
|
// update cg.predicted_player_state
|
|
|
|
CG_PredictPlayerState();
|
|
|
|
|
|
|
|
// build cg.refdef
|
|
|
|
CG_CalcViewValues();
|
|
|
|
|
2023-05-01 15:14:17 +02:00
|
|
|
// display the intermission
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg.snap->ps.pm_flags & PMF_INTERMISSION) {
|
2023-05-01 15:14:17 +02:00
|
|
|
if (cgs.gametype != GT_SINGLE_PLAYER) {
|
|
|
|
CG_ScoresDown_f();
|
2023-07-05 21:24:23 +02:00
|
|
|
} else if (cg.bIntermissionDisplay) {
|
|
|
|
if (cg.nextSnap) {
|
2023-08-28 18:35:47 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2023-05-01 15:14:17 +02:00
|
|
|
} else {
|
2023-08-28 18:35:47 +02:00
|
|
|
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
|
|
|
|
cgi.UI_ShowMenu("StatsScreen_Success", qfalse);
|
|
|
|
} else {
|
|
|
|
cgi.UI_ShowMenu("StatsScreen_Failed", qfalse);
|
|
|
|
}
|
2023-05-01 15:14:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
cgi.SendClientCommand("stats");
|
|
|
|
}
|
|
|
|
|
2023-05-22 12:31:38 +02:00
|
|
|
cg.bIntermissionDisplay = qtrue;
|
2023-07-05 21:24:23 +02:00
|
|
|
} else if (cg.bIntermissionDisplay) {
|
2023-05-01 15:14:17 +02:00
|
|
|
if (cgs.gametype != GT_SINGLE_PLAYER) {
|
|
|
|
CG_ScoresUp_f();
|
2023-07-05 21:24:23 +02:00
|
|
|
} else {
|
2023-08-28 18:35:47 +02:00
|
|
|
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:
|
2024-11-13 20:00:45 +01:00
|
|
|
cgi.UI_HideMenu("mission_success_1", qtrue);
|
2023-08-28 18:35:47 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
2024-11-13 20:00:45 +01:00
|
|
|
cgi.UI_HideMenu("mission_success_2", qtrue);
|
2023-08-28 18:35:47 +02:00
|
|
|
break;
|
|
|
|
case 3:
|
2024-11-13 20:00:45 +01:00
|
|
|
cgi.UI_HideMenu("mission_success_3", qtrue);
|
2023-08-28 18:35:47 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch (pMission->integer)
|
|
|
|
{
|
|
|
|
default:
|
|
|
|
case 0:
|
2024-11-13 20:00:45 +01:00
|
|
|
cgi.UI_HideMenu("mission_failed_1", qtrue);
|
2023-08-28 18:35:47 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
2024-11-13 20:00:45 +01:00
|
|
|
cgi.UI_HideMenu("mission_failed_2", qtrue);
|
2023-08-28 18:35:47 +02:00
|
|
|
break;
|
|
|
|
case 3:
|
2024-11-13 20:00:45 +01:00
|
|
|
cgi.UI_HideMenu("mission_failed_3", qtrue);
|
2023-08-28 18:35:47 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-05-01 15:14:17 +02:00
|
|
|
} else {
|
2023-08-28 18:35:47 +02:00
|
|
|
if (cgi.Cvar_Get("g_success", "", 0)->integer) {
|
2024-11-13 20:00:45 +01:00
|
|
|
cgi.UI_HideMenu("StatsScreen_Success", qtrue);
|
2023-08-28 18:35:47 +02:00
|
|
|
} else {
|
2024-11-13 20:00:45 +01:00
|
|
|
cgi.UI_HideMenu("StatsScreen_Failed", qtrue);
|
2023-08-28 18:35:47 +02:00
|
|
|
}
|
2023-05-01 15:14:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cg.bIntermissionDisplay = qfalse;
|
|
|
|
}
|
|
|
|
|
2025-01-22 20:52:05 +01:00
|
|
|
// Added in OPM
|
|
|
|
CG_ProcessPlayerModel();
|
|
|
|
|
2023-04-30 14:53:19 +02:00
|
|
|
// build the render lists
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!cg.hyperspace) {
|
|
|
|
CG_AddPacketEntities(); // after calcViewValues, so predicted player state is correct
|
2023-04-30 14:53:19 +02:00
|
|
|
CG_AddMarks();
|
|
|
|
}
|
|
|
|
|
|
|
|
// finish up the rest of the refdef
|
2023-08-28 17:52:49 +02:00
|
|
|
CG_SetupPortalSky();
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2023-04-30 14:53:19 +02:00
|
|
|
cg.refdef.time = cg.time;
|
|
|
|
memcpy(cg.refdef.areamask, cg.snap->areamask, sizeof(cg.refdef.areamask));
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 14:53:19 +02:00
|
|
|
// update audio positions
|
|
|
|
cgi.S_Respatialize(cg.snap->ps.clientNum, cg.SoundOrg, cg.SoundAxis);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 14:53:19 +02:00
|
|
|
// make sure the lagometerSample and frame timing isn't done twice when in stereo
|
2023-05-01 15:14:17 +02:00
|
|
|
if (stereoView != STEREO_RIGHT) {
|
2023-04-30 14:53:19 +02:00
|
|
|
CG_AddLagometerFrameInfo();
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 14:53:19 +02:00
|
|
|
CG_UpdateTestEmitter();
|
2023-05-01 15:14:17 +02:00
|
|
|
CG_AddPendingEffects();
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (!cg_hidetempmodels->integer) {
|
2023-04-30 14:53:19 +02:00
|
|
|
CG_AddTempModels();
|
2023-07-05 21:24:23 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-05-01 15:14:17 +02:00
|
|
|
if (vss_draw->integer) {
|
|
|
|
CG_AddVSSSources();
|
|
|
|
}
|
|
|
|
|
|
|
|
CG_AddBulletTracers();
|
|
|
|
CG_AddBulletImpacts();
|
2023-04-30 14:53:19 +02:00
|
|
|
CG_AddBeams();
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2023-05-01 15:14:17 +02:00
|
|
|
if (cg_acidtrip->integer) {
|
|
|
|
// lol disco
|
|
|
|
CG_AddLightShow();
|
|
|
|
}
|
|
|
|
|
2023-04-30 14:53:19 +02:00
|
|
|
// actually issue the rendering calls
|
|
|
|
CG_DrawActive(stereoView);
|
|
|
|
|
2023-07-05 21:24:23 +02:00
|
|
|
if (cg_stats->integer) {
|
2023-04-30 14:53:19 +02:00
|
|
|
cgi.Printf("cg.clientFrame:%i\n", cg.clientFrame);
|
|
|
|
}
|
|
|
|
}
|