raze/source/core/coreplayer.cpp

334 lines
11 KiB
C++

//-------------------------------------------------------------------------
/*
Copyright (C) 2023 Christoph Oelckers
Copyright (C) 2023 Mitchell Richters
This 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 the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//-------------------------------------------------------------------------
#include "gameinput.h"
//---------------------------------------------------------------------------
//
// Input constants used throughout associated functions.
//
//---------------------------------------------------------------------------
static constexpr double ROLL_TILTAVELSCALE = (1966426. / 12000000.);
static constexpr DAngle ROLL_TILTAVELMAX = DAngle::fromDeg(90. / 32. * 1.5);
static constexpr double ROLL_TILTRETURN = 15.;
static constexpr double YAW_LOOKINGSPEED = 801.5625;
static constexpr double YAW_ROTATESPEED = 63.28125;
static constexpr double YAW_LOOKRETURN = 7.5;
static constexpr double YAW_SPINSTAND = 675.;
static constexpr double YAW_SPINCROUCH = YAW_SPINSTAND * 0.5;
static constexpr double PITCH_LOOKSPEED = (269426662. / 1209103.);
static constexpr double PITCH_AIMSPEED = PITCH_LOOKSPEED * 0.5;
static constexpr double PITCH_CENTERSPEED = 10.7375;
static constexpr double PITCH_HORIZOFFSPEED = 4.375;
static constexpr DAngle PITCH_CNTRSINEOFFSET = DAngle90 / 8.;
static constexpr DAngle PITCH_HORIZOFFCLIMB = DAngle::fromDeg(-127076387. / 3344227.);
static constexpr DAngle PITCH_HORIZOFFPUSH = DAngle::fromDeg(14115687. / 31535389.);
//---------------------------------------------------------------------------
//
// CVARs to control view rolling.
//
//---------------------------------------------------------------------------
EXTERN_CVAR(Int, vr_mode)
CVAR(Float, cl_viewtiltscale, 1.f, CVAR_GLOBALCONFIG | CVAR_ARCHIVE);
CUSTOM_CVAR(Int, cl_viewtilting, 0, CVAR_GLOBALCONFIG | CVAR_ARCHIVE)
{
if (self < 0) self = 0;
else if (self > 3) self = 3;
}
//---------------------------------------------------------------------------
//
// Adjust player's pitch by way of keyboard input.
//
//---------------------------------------------------------------------------
void DCorePlayer::doPitchInput()
{
// Add player's mouse/device input.
if (cmd.ucmd.ang.Pitch.Degrees())
{
actor->spr.Angles.Pitch += cmd.ucmd.ang.Pitch * gameInput.SyncInput();
cmd.ucmd.actions &= ~SB_CENTERVIEW;
}
// Set up a myriad of bools.
const auto aimingUp = (cmd.ucmd.actions & SB_LOOK_UP) == SB_AIM_UP;
const auto aimingDown = (cmd.ucmd.actions & SB_LOOK_DOWN) == SB_AIM_DOWN;
const auto lookingUp = (cmd.ucmd.actions & SB_LOOK_UP) == SB_LOOK_UP;
const auto lookingDown = (cmd.ucmd.actions & SB_LOOK_DOWN) == SB_LOOK_DOWN;
// Process keyboard input.
if (const auto aiming = aimingDown - aimingUp)
{
actor->spr.Angles.Pitch += getTicrateAngle(PITCH_AIMSPEED * aiming);
cmd.ucmd.actions &= ~SB_CENTERVIEW;
}
if (const auto looking = lookingDown - lookingUp)
{
actor->spr.Angles.Pitch += getTicrateAngle(PITCH_LOOKSPEED * looking);
cmd.ucmd.actions |= SB_CENTERVIEW;
}
// Do return to centre.
if ((cmd.ucmd.actions & SB_CENTERVIEW) && !(lookingUp || lookingDown))
{
const auto pitch = abs(actor->spr.Angles.Pitch);
const auto scale = pitch > PITCH_CNTRSINEOFFSET ? (pitch - PITCH_CNTRSINEOFFSET).Cos() : 1.;
if (scaletozero(actor->spr.Angles.Pitch, PITCH_CENTERSPEED * scale))
cmd.ucmd.actions &= ~SB_CENTERVIEW;
}
// clamp before we finish, factoring in the player's view pitch offset.
const auto maximum = GetMaxPitch() - ViewAngles.Pitch * (ViewAngles.Pitch < nullAngle);
const auto minimum = GetMinPitch() - ViewAngles.Pitch * (ViewAngles.Pitch > nullAngle);
actor->spr.Angles.Pitch = clamp(actor->spr.Angles.Pitch, maximum, minimum);
}
//---------------------------------------------------------------------------
//
// Adjust player's yaw by way of keyboard input.
//
//---------------------------------------------------------------------------
void DCorePlayer::doYawInput()
{
// Add player's mouse/device input.
actor->spr.Angles.Yaw += cmd.ucmd.ang.Yaw * gameInput.SyncInput();
if (cmd.ucmd.actions & SB_TURNAROUND)
{
if (YawSpin == nullAngle)
{
// currently not spinning, so start a spin
YawSpin = -DAngle180;
}
cmd.ucmd.actions &= ~SB_TURNAROUND;
}
if (YawSpin < nullAngle)
{
// return spin to 0
DAngle add = getTicrateAngle(!(cmd.ucmd.actions & SB_CROUCH) ? YAW_SPINSTAND : YAW_SPINCROUCH);
YawSpin += add;
if (YawSpin > nullAngle)
{
// Don't overshoot our target. With variable factor this is possible.
add -= YawSpin;
YawSpin = nullAngle;
}
actor->spr.Angles.Yaw += add;
}
}
//---------------------------------------------------------------------------
//
// Player's slope tilt when playing without a mouse and on a slope.
//
//---------------------------------------------------------------------------
void DCorePlayer::doViewPitch(const bool climbing)
{
if (cl_slopetilting && canSlopeTilt())
{
const auto actorsect = actor->sector();
if (actorsect && (actorsect->floorstat & CSTAT_SECTOR_SLOPE)) // If the floor is sloped
{
// Get a point, 512 (64 for Blood) units ahead of player's position
const auto rotpt = actor->spr.pos.XY() + actor->spr.Angles.Yaw.ToVector() * (!isBlood() ? 32 : 4);
auto tempsect = actorsect;
updatesector(rotpt, &tempsect);
if (tempsect != nullptr) // If the new point is inside a valid sector...
{
// Get the floorz as if the new (x,y) point was still in
// your sector, unless it's Blood.
const double j = getflorzofslopeptr(actorsect, actor->spr.pos.XY());
const double k = getflorzofslopeptr(!isBlood() ? actorsect : tempsect, rotpt);
// If extended point is in same sector as you or the slopes
// of the sector of the extended point and your sector match
// closely (to avoid accidently looking straight out when
// you're at the edge of a sector line) then adjust horizon
// accordingly
if (actorsect == tempsect || (!isBlood() && abs(getflorzofslopeptr(tempsect, rotpt) - k) <= 4))
{
ViewAngles.Pitch -= maphoriz((j - k) * (!isBlood() ? 0.625 : 5.5));
}
}
}
}
if (cl_slopetilting && climbing)
{
// tilt when climbing but you can't even really tell it.
if (ViewAngles.Pitch > PITCH_HORIZOFFCLIMB)
ViewAngles.Pitch += getscaledangle(deltaangle(ViewAngles.Pitch, PITCH_HORIZOFFCLIMB), PITCH_HORIZOFFSPEED, PITCH_HORIZOFFPUSH);
}
else
{
// Make horizoff grow towards 0 since horizoff is not modified when you're not on a slope.
scaletozero(ViewAngles.Pitch, PITCH_HORIZOFFSPEED, PITCH_HORIZOFFPUSH);
}
}
//---------------------------------------------------------------------------
//
// Player's look left/right key angle handler.
//
//---------------------------------------------------------------------------
void DCorePlayer::doViewYaw()
{
// Process angle return to zeros.
scaletozero(ViewAngles.Yaw, YAW_LOOKRETURN);
scaletozero(ViewAngles.Roll, YAW_LOOKRETURN);
// Process keyboard input.
if (const auto looking = !!(cmd.ucmd.actions & SB_LOOK_RIGHT) - !!(cmd.ucmd.actions & SB_LOOK_LEFT))
{
ViewAngles.Yaw += getTicrateAngle(YAW_LOOKINGSPEED * looking);
ViewAngles.Roll += getTicrateAngle(YAW_ROTATESPEED * looking);
}
}
//---------------------------------------------------------------------------
//
// View tilting effects, mostly for Exhumed to enhance its gameplay feel.
//
//---------------------------------------------------------------------------
void DCorePlayer::doRollInput(const bool bUnderwater)
{
// Allow viewtilting if we're not in a VR mode.
if (!vr_mode)
{
// Scale/attenuate tilting based on player actions.
const auto nMaxVel = GetMaxInputVel();
const auto rollAmp = cl_viewtiltscale / (1 + bUnderwater);
const auto runScale = 1. / (1 + !(cmd.ucmd.actions & SB_RUN));
const auto strafeScale = 1 + !!cmd.ucmd.vel.Y;
if (cl_viewtilting == 1)
{
// Console-like yaw rolling. Adjustment == ~(90/32) for keyboard turning. Clamp is 1.5x this value.
const auto rollAdj = cmd.ucmd.ang.Yaw * ROLL_TILTAVELSCALE * rollAmp;
const auto rollMax = ROLL_TILTAVELMAX * cl_viewtiltscale;
scaletozero(actor->spr.Angles.Roll, ROLL_TILTRETURN);
actor->spr.Angles.Roll = clamp(actor->spr.Angles.Roll + rollAdj, -rollMax, rollMax);
}
else if (cl_viewtilting == 2)
{
// Quake-like strafe rolling. Adjustment == (90/48) for running keyboard strafe.
const auto rollAdj = RollVel * strafeScale * rollAmp;
const auto rollMax = nMaxVel * runScale * cl_viewtiltscale;
actor->spr.Angles.Roll = DAngle::fromDeg(clamp(rollAdj, -rollMax, rollMax) * (1.875 / nMaxVel));
}
else if (cl_viewtilting == 3)
{
// Movement rolling from player's velocity. Adjustment == (90/48) for running keyboard strafe.
const auto rollAdj = GetInputVelocity().Rotated(-actor->spr.Angles.Yaw).Y * strafeScale * rollAmp;
const auto rollMax = nMaxVel * runScale * cl_viewtiltscale;
actor->spr.Angles.Roll = DAngle::fromDeg(clamp(rollAdj, -rollMax, rollMax) * (1.875 / nMaxVel));
}
else
{
// Always reset roll if we're not tilting at all.
actor->spr.Angles.Roll = nullAngle;
}
}
else
{
// Add player's device input.
actor->spr.Angles.Roll += cmd.ucmd.ang.Roll * gameInput.SyncInput();
}
}
//---------------------------------------------------------------------------
//
// Stat for console player's position/angles.
//
//---------------------------------------------------------------------------
ADD_STAT(coord)
{
FString out;
const auto p = PlayerArray[myconnectindex];
if (const auto pActor = p->GetActor())
{
out.AppendFormat("X: %.4f ", pActor->spr.pos.X);
out.AppendFormat("Y: %.4f ", pActor->spr.pos.Y);
out.AppendFormat("Z: %.4f\n", pActor->spr.pos.Z);
out.AppendFormat("Yaw: %.4f ", pActor->spr.Angles.Yaw.Degrees());
out.AppendFormat("Pitch: %.4f ", pActor->spr.Angles.Pitch.Degrees());
out.AppendFormat("Roll: %.4f\n", pActor->spr.Angles.Roll.Degrees());
out.AppendFormat("View Yaw: %.4f ", p->ViewAngles.Yaw.Degrees());
out.AppendFormat("View Pitch: %.4f ", p->ViewAngles.Pitch.Degrees());
out.AppendFormat("View Roll: %.4f\n", p->ViewAngles.Roll.Degrees());
}
return out;
}
//---------------------------------------------------------------------------
//
// CCMD to warp console player to the given coordinates.
//
//---------------------------------------------------------------------------
CCMD(warptocoords)
{
if (netgame)
{
Printf("warptocoords cannot be used in multiplayer.\n");
return;
}
if (argv.argc() < 4)
{
Printf("warptocoords [x] [y] [z] [yaw] (optional) [pitch] (optional): warps the player to the specified coordinates\n");
return;
}
if (gamestate != GS_LEVEL)
{
Printf("warptocoords: must be in a level\n");
return;
}
if (const auto pActor = PlayerArray[myconnectindex]->GetActor())
{
pActor->spr.pos = DVector3(atof(argv[1]), atof(argv[2]), atof(argv[3]));
if (argv.argc() > 4) pActor->spr.Angles.Yaw = DAngle::fromDeg(atof(argv[4]));
if (argv.argc() > 5) pActor->spr.Angles.Pitch = DAngle::fromDeg(atof(argv[5]));
pActor->backuploc();
}
}