mirror of
https://github.com/nzp-team/quakec.git
synced 2024-12-01 00:12:05 +00:00
303 lines
No EOL
10 KiB
C++
303 lines
No EOL
10 KiB
C++
/*
|
|
client/view_model.qc
|
|
|
|
View Model Drawing and Manipulation.
|
|
|
|
Copyright (C) 2021-2024 NZ:P Team
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
vector current_viewmodel_ads_position;
|
|
vector viewmodel_slack_distance;
|
|
vector last_viewangles;
|
|
|
|
#define VIEWMODEL_ADS_TIME_FACTOR (16 * frametime) // We want this to be server dependent.
|
|
#define VIEWMODEL_ANGLE_SLACK_FACTOR (3)
|
|
|
|
//MOVEME
|
|
float(float a, float b, float mix) lerp =
|
|
{
|
|
if (mix <= 0) return a;
|
|
if (mix >= 1) return b;
|
|
return (b * mix + a * ( 1 - mix ) );
|
|
};
|
|
|
|
//MOVEME
|
|
float(float value, float minValue, float maxValue) clamp = {
|
|
if (value < minValue) return minValue;
|
|
else if (value > maxValue) return maxValue;
|
|
|
|
return value;
|
|
};
|
|
|
|
//MOVEME
|
|
float(float source, float target, float smoothing, float dt) damp =
|
|
{
|
|
return lerp(source, target, 1 - pow(smoothing, dt));
|
|
};
|
|
|
|
//
|
|
// ViewModel_CalcBob()
|
|
// Blubs' refactored V_CalcBob reimplemented in CSQC.
|
|
// Edited to also add idle bobbing if low speed.
|
|
//
|
|
float(float speed, float which) ViewModel_CalcBob =
|
|
{
|
|
float sprint = 1;
|
|
float client_zoom = getstatf(STAT_WEAPONZOOM);
|
|
float cl_bobup_value = cvar("cl_bobup");
|
|
float cl_bobside_value = cvar("cl_bobside");
|
|
|
|
// Bob idle-y, instead of presenting as if in-motion.
|
|
if (speed < 0.1) {
|
|
// If we're zoomed in, we only want idle bobbing to be very subtle..
|
|
if (client_zoom == 1)
|
|
speed = 0.05;
|
|
else
|
|
speed = 0.25;
|
|
|
|
if (which == 0)
|
|
return cl_bobup_value * 10 * speed * (sprint * sprint) * sin(cltime * 3.25 * sprint);
|
|
else
|
|
return cl_bobside_value * 50 * speed * (sprint * sprint * sprint) * sin((cltime * sprint) - (M_PI * 0.25));
|
|
}
|
|
// Normal walk/sprint bob.
|
|
else {
|
|
if (client_zoom == 3)
|
|
sprint = 1.8;
|
|
|
|
if (which == 0)
|
|
return cl_bobup_value * 36 * speed * (sprint * sprint) * sin(cltime * 12.5 * sprint);
|
|
else
|
|
return cl_bobside_value * 36 * speed * (sprint * sprint * sprint) * sin((cltime * 6.25 * sprint) - (M_PI * 0.25));
|
|
}
|
|
};
|
|
|
|
//
|
|
// ViewModel_CalcViewAngleDifference()
|
|
// Returns the difference in client viewangles between this frame
|
|
// and last.
|
|
//
|
|
vector() ViewModel_CalcViewAngleDifference =
|
|
{
|
|
if (last_viewangles == '0 0 0')
|
|
last_viewangles = getviewprop(VF_ANGLES);
|
|
|
|
vector cl_viewangles = getviewprop(VF_ANGLES);
|
|
vector difference = last_viewangles - cl_viewangles;
|
|
last_viewangles = cl_viewangles;
|
|
|
|
// Invert the X (up/down) axis, so that viewmodel
|
|
// accels downward when camera moves up, and vice versa.
|
|
difference[0] = -difference[0];
|
|
return difference;
|
|
};
|
|
|
|
//
|
|
// ViewModel_CalcSlack()
|
|
// Calculates "slack" -- how far behind the viewents
|
|
// should fall behind the camera. Appended to viewent's angles.
|
|
//
|
|
vector() ViewModel_CalcSlack =
|
|
{
|
|
// Calculate the target slack distance based on the view angle difference
|
|
vector target_slack_distance = ViewModel_CalcViewAngleDifference();
|
|
target_slack_distance[0] *= VIEWMODEL_ANGLE_SLACK_FACTOR;
|
|
target_slack_distance[1] *= VIEWMODEL_ANGLE_SLACK_FACTOR;
|
|
target_slack_distance[2] *= VIEWMODEL_ANGLE_SLACK_FACTOR;
|
|
|
|
for(float i = 0; i < 2; i++) {
|
|
// Apply smoothing to the viewmodel_slack_distance
|
|
viewmodel_slack_distance[i] = damp(viewmodel_slack_distance[i], target_slack_distance[i], 0.1, clframetime * 4);
|
|
|
|
// Clamp the values to a maximum threshold
|
|
// Sorta nasty hack.. or something.. if the client is under 60FPS clamp at a lower value
|
|
if (clframetime > 0.016)
|
|
viewmodel_slack_distance[i] = clamp(viewmodel_slack_distance[i], -0.2, 0.2);
|
|
else
|
|
viewmodel_slack_distance[i] = clamp(viewmodel_slack_distance[i], -0.5, 0.5);
|
|
}
|
|
|
|
return viewmodel_slack_distance;
|
|
};
|
|
|
|
//
|
|
// ViewModel_Animate(viewent)
|
|
// Interpolation and animation for specified view entity.
|
|
//
|
|
void(entity viewent) ViewModel_Animate =
|
|
{
|
|
float new_frame, new_model, anim_duration = 0;
|
|
|
|
if (viewent == cl_viewent) {
|
|
new_frame = getstatf(STAT_WEAPONFRAME);
|
|
new_model = getstatf(STAT_WEAPONMODELI);
|
|
anim_duration = getstatf(STAT_WEAPONDURATION);
|
|
} else { // Assumes cl_viewent2
|
|
new_model = getstatf(STAT_WEAPON2MODELI);
|
|
anim_duration = getstatf(STAT_WEAPON2DURATION);
|
|
|
|
// If our current weapon is not dual-wielded, we want weapon2frame
|
|
// to always match standard weapon frames.
|
|
if (!IsDualWeapon(getstatf(STAT_ACTIVEWEAPON)))
|
|
new_frame = getstatf(STAT_WEAPONFRAME);
|
|
else
|
|
new_frame = getstatf(STAT_WEAPON2FRAME);
|
|
}
|
|
|
|
if (!anim_duration) anim_duration = 0.1; // Default to 10fps.
|
|
|
|
// Server requested a model change
|
|
if (new_model != viewent.modelindex) {
|
|
viewent.modelindex = new_model;
|
|
|
|
// Don't lerp new models. Fixes perceieved stutter.
|
|
viewent.frame = new_frame;
|
|
|
|
// If we switched to a different weapon model,
|
|
// make sure to update the HUD.
|
|
if (getmodelindex(GetWeaponModel(getstatf(STAT_ACTIVEWEAPON), false)) == viewent.modelindex)
|
|
HUD_Change_time = time + 6;
|
|
|
|
// Update the skin.
|
|
viewent.skin = getstatf(STAT_WEAPONSKIN);
|
|
}
|
|
|
|
// Server requested a frame change
|
|
if (new_frame != viewent.frame) {
|
|
viewent.frame2 = viewent.frame;
|
|
viewent.frame2time = viewent.frame1time;
|
|
viewent.frame = new_frame;
|
|
viewent.frame1time = 0;
|
|
viewent.lerpfrac = 1;
|
|
}
|
|
else {
|
|
viewent.frame = new_frame;
|
|
}
|
|
|
|
viewent.lerpfrac -= frametime * (1/anim_duration);
|
|
viewent.frame1time += frametime;
|
|
};
|
|
|
|
//
|
|
// ViewModel_MotionBob(viewent)
|
|
// Bobs the specified view entity with ViewModel_CalcBob.
|
|
//
|
|
void(entity viewent) ViewModel_MotionBob =
|
|
{
|
|
// If the server is paused, early-out..
|
|
if (serverkey(SERVERKEY_PAUSESTATE) == "1")
|
|
return;
|
|
|
|
float client_speed;
|
|
|
|
// Calculate a rough estimate of the client's speed
|
|
client_speed = (0.2 + sqrt((playerVelocity[0] * playerVelocity[0]) + (playerVelocity[1] * playerVelocity[1])))/280;
|
|
|
|
// Run the bob calculation
|
|
float bob, bobside;
|
|
bob = ViewModel_CalcBob(client_speed, 0);
|
|
bobside = ViewModel_CalcBob(client_speed, 1);
|
|
|
|
// Set as start of view entity's origin offset.
|
|
viewent.origin[2] = bob*0.5;
|
|
viewent.origin[1] = bobside * 0.4;
|
|
};
|
|
|
|
//
|
|
// ViewModel_GetADSPosition()
|
|
// Returns a vector representing current offset
|
|
// progress on Aiming Down the Sight.
|
|
//
|
|
vector() ViewModel_GetADSPosition =
|
|
{
|
|
vector ads_offset = '0 0 0';
|
|
float client_zoom = getstatf(STAT_WEAPONZOOM);
|
|
if (client_zoom == 1 || client_zoom == 2) {
|
|
// These are ordered differently in QuakeC because ??
|
|
// right, up, forward
|
|
vector temp_adsofs = GetWeaponADSOfs(getstatf(STAT_ACTIVEWEAPON));
|
|
|
|
// Translate to proper order, and re-gain precision.
|
|
ads_offset[0] = temp_adsofs[2]/1000;
|
|
ads_offset[1] = -temp_adsofs[0]/1000; // Y needs to be negative here -- matches Blender output, discrepancy from glQuake.
|
|
ads_offset[2] = temp_adsofs[1]/1000;
|
|
}
|
|
|
|
// Interpolate values in/out of ADS over time.
|
|
current_viewmodel_ads_position[0] += (ads_offset[0] - current_viewmodel_ads_position[0]) * VIEWMODEL_ADS_TIME_FACTOR;
|
|
current_viewmodel_ads_position[1] += (ads_offset[1] - current_viewmodel_ads_position[1]) * VIEWMODEL_ADS_TIME_FACTOR;
|
|
current_viewmodel_ads_position[2] += (ads_offset[2] - current_viewmodel_ads_position[2]) * VIEWMODEL_ADS_TIME_FACTOR;
|
|
|
|
// Limit the vector to avoid bouncing back and forth on low framerates.
|
|
if (client_zoom == 1 || client_zoom == 2) {
|
|
if (current_viewmodel_ads_position[0] > ads_offset[0]) current_viewmodel_ads_position[0] = ads_offset[0];
|
|
if (current_viewmodel_ads_position[1] > ads_offset[1]) current_viewmodel_ads_position[1] = ads_offset[1];
|
|
if (current_viewmodel_ads_position[2] > ads_offset[2]) current_viewmodel_ads_position[2] = ads_offset[2];
|
|
} else {
|
|
if (current_viewmodel_ads_position[0] < 0) current_viewmodel_ads_position[0] = 0;
|
|
if (current_viewmodel_ads_position[1] < 0) current_viewmodel_ads_position[1] = 0;
|
|
if (current_viewmodel_ads_position[2] < 0) current_viewmodel_ads_position[2] = 0;
|
|
}
|
|
|
|
// Return progress on Zoom position.
|
|
return current_viewmodel_ads_position;
|
|
};
|
|
|
|
//
|
|
// ViewModel_Draw()
|
|
// Performs drawing routine for cl_viewent and cl_viewent2
|
|
//
|
|
void() ViewModel_Draw =
|
|
{
|
|
// Spawn the model entities if they do not already exist..
|
|
if (!cl_viewent) {
|
|
cl_viewent = spawn();
|
|
cl_viewent2 = spawn();
|
|
cl_viewent.renderflags = cl_viewent2.renderflags = RF_VIEWMODEL;
|
|
}
|
|
|
|
// If we're zoomed in with a Sniper scope or r_drawviewmodel is false, early-out.
|
|
if (!cvar("r_drawviewmodel") || getstatf(STAT_WEAPONZOOM) == 2)
|
|
return;
|
|
|
|
cl_viewent.origin = cl_viewent2.origin = '0 0 0';
|
|
|
|
// Bob while in motion
|
|
ViewModel_MotionBob(cl_viewent);
|
|
ViewModel_MotionBob(cl_viewent2);
|
|
|
|
// Adjust viewmodel position based on ADS value.
|
|
vector viewent_ads_position = ViewModel_GetADSPosition();
|
|
cl_viewent.origin += viewent_ads_position;
|
|
cl_viewent2.origin += viewent_ads_position;
|
|
|
|
// Animate and Interpolate
|
|
ViewModel_Animate(cl_viewent);
|
|
ViewModel_Animate(cl_viewent2);
|
|
|
|
// Have the View Model "lag behind" the camera a bit.
|
|
cl_viewent.angles = cl_viewent2.angles = ViewModel_CalcSlack();
|
|
|
|
// Send the viewents to engine for drawing.
|
|
addentity(cl_viewent);
|
|
addentity(cl_viewent2);
|
|
}; |