nuclide/src/shared/player_pmove.qc

674 lines
17 KiB
C++

/*
* Copyright (c) 2016-2024 Vera Visions LLC.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
vector saved_input_movevalues;
int saved_input_buttons;
/* distance after which we take damage */
#ifndef PHY_FALLDMG_TYPE
#define PHY_FALLDMG_TYPE 1
#endif
#ifndef PHY_DIVESPEED_WATER
#define PHY_DIVESPEED_WATER 100
#endif
#ifndef PHY_DIVESPEED_SLIME
#define PHY_DIVESPEED_SLIME 80
#endif
#ifndef PHY_DIVESPEED_LAVA
#define PHY_DIVESPEED_LAVA 50
#endif
void
ncPlayer::Physics_Fall(float flDownforce)
{
float minFallDistance = g_fallDamageDecl.GetFloat("minFallDistance");
float maxFallDistance = g_fallDamageDecl.GetFloat("maxFallDistance");
float minLandDistance = g_fallDamageDecl.GetFloat("minLandDistance");
float punchAngleScale = g_fallDamageDecl.GetFloat("punchAngleScale");
float punchSpringScale = g_fallDamageDecl.GetFloat("punchSpringScale");
int fixedDamage = g_fallDamageDecl.GetInteger("damage");
if (punchAngleScale > 0.0f) {
/* apply some predicted punch to the player */
if (flDownforce >= minFallDistance)
punchangle += [punchAngleScale,0,(input_sequence & 1) ? punchAngleScale : -punchAngleScale];
else if (flDownforce >= minLandDistance)
punchangle += [punchAngleScale,0,0];
} else if (punchSpringScale > 0.0f) {
/* apply some predicted punch to the player */
if (flDownforce >= minFallDistance)
punchvelocity += [punchSpringScale,0,(input_sequence & 1) ? punchSpringScale : -punchSpringScale];
else if (flDownforce >= minLandDistance)
punchvelocity += [punchSpringScale,0,0];
}
/* basic server-side falldamage */
#ifdef SERVER
/* if we've reached a fallheight of minFallDistance qu, start applying damage */
if (flDownforce >= minFallDistance) {
float finalDamage;
if (fixedDamage != 0i) {
finalDamage = fixedDamage;
} else {
/* distance of A to B decides how much of 100 HP dmg we get*/
finalDamage = (flDownforce - minFallDistance) * (100 / (maxFallDistance - minFallDistance));
}
ncDict damageDecl = spawn(ncDict);
damageDecl.AddKey("damage", itos((int)finalDamage));
damageDecl.AddKey("noArmor", "1");
Damage(this, world, damageDecl, 1.0, g_vec_null, GetOrigin() + [0,0, mins[2]]);
remove(damageDecl);
//Damage_Apply(this, world, fFallDamage, 0, DMG_FALL | DMG_SKIP_ARMOR);
StartSoundDef(m_sndFall, CHAN_VOICE, true);
} else if (flDownforce >= minLandDistance) {
StartSoundDef(m_sndLandHard, CHAN_VOICE, true);
} else {
StartSoundDef(m_sndLandSoft, CHAN_VOICE, true);
}
#endif
}
void
ncPlayer::Physics_Crouch(void)
{
bool crouchFix = false;
vector testOrigin = origin;
vector testMins = g_vec_null;
vector testMaxs = g_vec_null;
bool stateChanged = false;
if (GetMovetype() != MOVETYPE_WALK)
return;
if (CanCrouch() && input_buttons & INPUT_CROUCH) {
if (!IsCrouching()) {
testMins = m_pmoveVars.GetCrouchMins();
testMaxs = m_pmoveVars.GetCrouchMaxs();
if (PMove_IsStuck(this, origin, testMins, testMaxs) == false) {
AddVFlags(VFL_CROUCHING);
stateChanged = true;
/*printf("Now crouch.\n");*/
}
}
} else {
/* if we aren't ducking any longer, attempt to stand up */
if (IsCrouching()) {
/*printf("No longer crouch.\n");*/
/* if they're going prone mode, we'll have to test that */
if (input_buttons & INPUT_PRONE) {
testMins = m_pmoveVars.GetProneMins();
testMaxs = m_pmoveVars.GetProneMaxs();
/*printf("Want to prone.\n");*/
} else {
testMins = m_pmoveVars.GetStandingMins();
testMaxs = m_pmoveVars.GetStandingMaxs();
/*printf("Want to stand.\n");*/
}
testOrigin[2] = (origin[2] + mins[2]) - testMins[2];
/* will we fit here? if not, don't remove the CROUCH flag */
if (PMove_IsStuck(this, testOrigin, testMins, testMaxs) == false) {
RemoveVFlags(VFL_CROUCHING);
stateChanged = true;
/*printf("Fill fit.\n");*/
} else if (PMove_IsStuck(this, testOrigin + [0,0,18], testMins, testMaxs) == false) {
RemoveVFlags(VFL_CROUCHING);
stateChanged = true;
crouchFix = true;
/*printf("Fill have to nudge up to 18 units.\n");*/
}
}
}
/* state didn't change, don't update size */
if (!stateChanged)
return;
if (IsCrouching()) {
mins = m_pmoveVars.GetCrouchMins();
maxs = m_pmoveVars.GetCrouchMaxs();
/*printf("State change finished.\n");*/
} else {
mins = testMins;
maxs = testMaxs;
if (crouchFix == true && PMove_IsStuck(this, testOrigin, testMins, testMaxs)) {
/* check if we can get unstuck by testing up to a few units up */
for (int i = 0; i < 18; i++) {
if (PMove_IsStuck(this, testOrigin, testMins, testMaxs) == false) {
/*printf("State change finished after %i units.\n", i);*/
break;
}
testOrigin[2] += 1;
}
}
SetOrigin(testOrigin);
}
}
void
ncPlayer::Physics_Prone(void)
{
bool proneFix = false;
vector testOrigin = origin;
vector testMins = g_vec_null;
vector testMaxs = g_vec_null;
bool stateChanged = false;
if (GetMovetype() != MOVETYPE_WALK)
return;
if (CanProne() && input_buttons & INPUT_PRONE) {
if (!IsProne()) {
testMins = m_pmoveVars.GetProneMins();
testMaxs = m_pmoveVars.GetProneMaxs();
if (PMove_IsStuck(this, origin, testMins, testMaxs) == false) {
AddVFlags(VFL_PRONE);
stateChanged = true;
/*printf("Now prone.\n");*/
}
}
} else {
/* if we aren't holding down duck anymore and 'attempt' to stand up, prevent it */
if (IsProne()) {
/*printf("No longer prone.\n");*/
/* if they're going prone mode, we'll have to test that */
if (input_buttons & INPUT_CROUCH) {
testMins = m_pmoveVars.GetCrouchMins();
testMaxs = m_pmoveVars.GetCrouchMaxs();
/*printf("Want to crouch.\n");*/
} else {
testMins = m_pmoveVars.GetStandingMins();
testMaxs = m_pmoveVars.GetStandingMaxs();
/*printf("Want to stand.\n");*/
}
testOrigin[2] = (origin[2] + mins[2]) - testMins[2];
/* will we fit? */
if (PMove_IsStuck(this, testOrigin, testMins, testMaxs) == false) {
RemoveVFlags(VFL_PRONE);
stateChanged = true;
/*printf("Will fit.\n");*/
} else if (PMove_IsStuck(this, testOrigin + [0, 0, 18], testMins, testMaxs) == false) {
RemoveVFlags(VFL_PRONE);
proneFix = true;
stateChanged = true;
/*printf("Fill have to nudge up to 18 units.\n");*/
}
}
}
/* prevent expensive operations. */
if (!stateChanged)
return;
if (IsProne()) {
mins = m_pmoveVars.GetProneMins();
maxs = m_pmoveVars.GetProneMaxs();
/*printf("State change finished.\n");*/
} else {
mins = testMins;
maxs = testMaxs;
origin = testOrigin;
if (proneFix == true && PMove_IsStuck(this, origin, mins, maxs)) {
/* check if we can get unstuck by testing up to a few units up */
for (int i = 1; i < 18; i++) {
if (PMove_IsStuck(this, origin, mins, maxs) == false) {
/*printf("State change finished after %i units.\n", i);*/
break;
}
origin[2] += 1;
}
}
SetOrigin(origin);
}
}
void
ncPlayer::Physics_Jump(void)
{
if (GetMovetype() != MOVETYPE_WALK)
return;
/* we're underwater... */
if (WaterLevel() >= 2) {
/* different water contents allow for different speeds */
if (WaterLevel() == CONTENT_WATER)
velocity[2] = PHY_DIVESPEED_WATER;
else if (WaterLevel() == CONTENT_SLIME)
velocity[2] = PHY_DIVESPEED_SLIME;
else
velocity[2] = PHY_DIVESPEED_LAVA;
} else {
/* standard jump here */
if (GetFlags() & FL_ONGROUND)
velocity[2] += m_pmoveVars.pm_jumpheight;
}
}
/* check if we're elligible to jump */
void
ncPlayer::Physics_CheckJump(float premove)
{
/* unset jump-key whenever it's not set */
if (!(input_buttons & INPUT_JUMP)) {
AddFlags(FL_JUMPRELEASED);
return;
}
if (GetFlags() & FL_WATERJUMP)
return;
if (!(GetFlags() & FL_ONGROUND))
return;
/* if a player wants to be able to hold jump, let them */
if (!(infokey(this, "autojump") == "1"))
if (HasFlags(FL_JUMPRELEASED) == false)
return;
if (input_buttons & INPUT_JUMP && premove) {
if (velocity[2] < 0) {
velocity[2] = 0;
}
Physics_Jump();
RemoveFlags(FL_ONGROUND);
RemoveFlags(FL_JUMPRELEASED);
}
}
/* establish the right size and camera position */
void
ncPlayer::Physics_SetViewParms(void)
{
/* cheap operations */
if (IsCrouching()) {
view_ofs = m_pmoveVars.GetCrouchViewOffset();
} else if (IsProne()) {
view_ofs = m_pmoveVars.GetProneViewOffset();
} else {
view_ofs = m_pmoveVars.GetStandingViewOffset();
}
}
void
ncPlayer::Physics_WaterJump(void)
{
vector vecStart;
/* bit above waist height */
vecStart = GetOrigin();
vecStart[2] += 8;
/* look 24 qu ahead for a surface */
makevectors(v_angle);
traceline(vecStart, vecStart + (v_forward * 24), MOVE_NORMAL, this);
/* we've hit a surface */
if (trace_fraction < 1.0) {
/* let's check if we can potentially climb up */
vecStart[2] += maxs[2];
traceline(vecStart, vecStart + (v_forward * 24), MOVE_NORMAL, this);
/* there's nothing preventing us from putting our hands up here */
if (trace_fraction == 1.0) {
velocity[2] = m_pmoveVars.pm_waterjumpheight;
AddFlags(FL_WATERJUMP);
RemoveFlags(FL_JUMPRELEASED);
return;
}
}
}
/* handle your time underwater */
void
ncPlayer::Physics_WaterMove(void)
{
if (GetMovetype() == MOVETYPE_NOCLIP) {
return;
}
#ifdef SERVER
if (GetHealth() < 0) {
return;
}
if (WaterLevel() > 0) {
if (watertype == CONTENT_LAVA) {
if (m_flPainTime < time) {
Damage(world, world, g_lavaDamageDecl, 1.0f, g_vec_null, GetOrigin());
m_flPainTime = time + g_lavaDecl.GetFloat("dmgtime");
}
} else if (watertype == CONTENT_SLIME) {
if (m_flPainTime < time) {
Damage(world, world, g_slimeDamageDecl, 1.0f, g_vec_null, GetOrigin());
m_flPainTime = time + g_slimeDecl.GetFloat("dmgtime");
}
}
}
/* we've just exited water */
if (WaterLevel() != 3) {
if (m_flUnderwaterTime < time) {
#warning Read sound from player decl
StartSoundDef(m_sndAirGaspHeavy, CHAN_BODY, true);
} else if (m_flUnderwaterTime < time + 9) {
StartSoundDef(m_sndAirGaspLight, CHAN_BODY, true);
}
#warning Read lung capacity for water from typeInfo water
m_flUnderwaterTime = time + 12;
} else if (m_flUnderwaterTime < time) {
/* we've been underwater... for too long. */
if (m_flPainTime < time) {
Damage(world, world, g_waterDamageDecl, 1.0f, g_vec_null, GetOrigin());
m_flPainTime = time + g_waterDecl.GetFloat("dmgtime");
}
}
#endif
if (!WaterLevel()){
if (GetFlags() & FL_INWATER) {
#ifdef SERVER
StartSoundDef(m_sndWaterExit, CHAN_BODY, true);
#endif
RemoveFlags(FL_INWATER);
}
return;
}
if (!(GetFlags() & FL_INWATER)) {
#ifdef SERVER
StartSoundDef(m_sndWaterEnter, CHAN_BODY, true);
m_flPainTime = 0;
#endif
AddFlags(FL_INWATER);
}
/* we might need to apply extra-velocity to get out of water-volumes */
if (WaterLevel() >= 2) {
Physics_WaterJump();
}
}
float
ncPlayer::Physics_MaxSpeed(void)
{
float wishSpeed = 0.0f;
float runSpeed = m_pmoveVars.pm_runspeed;
float maxStamina = m_pmoveVars.pm_stamina;
float crouchSpeed = m_pmoveVars.pm_crouchspeed;
float walkSpeed = m_pmoveVars.pm_walkspeed;
float proneSpeed = m_pmoveVars.pm_pronespeed;
float speedMod = 1.0f;
if (m_activeWeapon) {
speedMod = m_activeWeapon.m_flSpeedMod;
}
if (CanSprint() && IsSprinting()) {
if (m_flStamina < maxStamina) {
float slowDownThresh = m_pmoveVars.pm_staminathreshold;
if ((m_flStamina + slowDownThresh) > maxStamina) {
float delta = (m_flStamina + slowDownThresh) - maxStamina;
/* change maxValue to something between 1.0 and 1.5 */
runSpeed = lerp(runSpeed, walkSpeed, delta/slowDownThresh);
}
}
wishSpeed = (m_flStamina >= maxStamina) ? walkSpeed : runSpeed;
} else if (IsCrouching()) {
wishSpeed = crouchSpeed;
} else if (IsProne()) {
wishSpeed = proneSpeed;
} else {
wishSpeed = walkSpeed;
}
return wishSpeed * speedMod;
}
void
ncPlayer::Physics_InputPreMove(void)
{
ncVehicle currentVehicle = (ncVehicle)vehicle;
bool canMove = true;
bool canFire = true;
/* find all the valid ways to freeze a player... */
if (currentVehicle) {
if (currentVehicle.PreventPlayerMovement() == true)
canMove = false;
}
if (vv_flags & VFL_FROZEN || movetype == MOVETYPE_NONE) {
canMove = false;
}
/* freeze in place */
if (canMove == false) {
input_movevalues = [0,0,0];
input_buttons &= ~INPUT_JUMP;
}
/* freeze in place */
if (vv_flags & VFL_NOATTACK) {
input_buttons &= ~INPUT_PRIMARY;
input_buttons &= ~INPUT_RELOAD;
}
/* when pressing the 'use' button, we also walk slower for precision */
if (input_buttons & INPUT_USE) {
input_movevalues *= 0.5;
}
/* clamp movement values to max speed */
{
float wishSpeed = vlen(input_movevalues);
if (wishSpeed > maxspeed) {
wishSpeed = maxspeed;
}
input_movevalues = normalize(input_movevalues) * wishSpeed;
}
/* suppress crouching in vehicles */
if (currentVehicle) {
if (currentVehicle.CanDriverCrouch() == false) {
input_buttons &= ~INPUT_CROUCH;
input_buttons &= ~INPUT_PRONE;
}
}
}
/* timers get processed here after physics are run */
void
ncPlayer::Physics_InputPostMove(void)
{
float punch;
/* timers, these are predicted and shared across client and server */
w_attack_next = max(0, w_attack_next - input_timelength);
w_reload_next = max(0, w_reload_next - input_timelength);
w_idle_next = max(0, w_idle_next - input_timelength);
weapontime += input_timelength;
if (input_buttons & INPUT_SPRINT) {
m_flStamina += input_timelength;
} else {
float toSubtract = input_timelength;
toSubtract *= m_pmoveVars.pm_staminarate;
m_flStamina = max(0, m_flStamina - toSubtract);
}
if (m_flStamina > m_pmoveVars.pm_stamina) {
m_flStamina = m_pmoveVars.pm_stamina;
}
if (m_pmoveVars.pm_runfiring == 0 && input_buttons & INPUT_SPRINT) {
input_buttons &= ~INPUT_PRIMARY;
input_buttons &= ~INPUT_SECONDARY;
input_buttons &= ~INPUT_RELOAD;
}
//printf("stamina: %f\n", m_flStamina);
#if 0
#else
float flDamp;
float flForce;
if ( vlen( punchvelocity ) > 0.001 ) {
punchangle += punchvelocity * input_timelength;
flForce = bound( 0, input_timelength * 65, 2 );
flDamp = 1 - ( input_timelength * 9 );
if ( flDamp < 0 ) {
flDamp = 0;
}
punchvelocity *= flDamp;
punchvelocity -= punchangle * flForce;
punchangle[0] = bound( -89, punchangle[0], 89 );
punchangle[1] = bound( -179, punchangle[1], 179 );
punchangle[2] = bound( -89, punchangle[2], 89 );
} else if (vlen( punchangle ) > 0.001) {
punch = max(0, 1.0f - (input_timelength * 4));
punchangle[0] *= punch;
punchangle[1] *= punch;
punchangle[2] *= punch;
}
#endif
/* player animation code */
UpdatePlayerAnimation(input_timelength);
ProcessInput();
}
/* the main physics routine, the head */
void
ncPlayer::Physics_Run(void)
{
float flFallVel = (flags & FL_ONGROUND) ? 0 : -velocity[2];
float flBaseVel = basevelocity[2];
bool wasOnGround = (flags & FL_ONGROUND) ? true : false;
bool sprintFail = false;
saved_input_movevalues = input_movevalues;
saved_input_buttons = input_buttons;
sprintFail = ((input_buttons & INPUT_CROUCH) || (input_buttons & INPUT_PRONE)) ? true : false;
if (input_buttons & INPUT_SPRINT && sprintFail == false) {
AddVFlags(VFL_SPRINTING);
} else {
RemoveVFlags(VFL_SPRINTING);
}
maxspeed = Physics_MaxSpeed();
#ifdef SERVER
//printf("SERVER: %v; max: %f; fric: %f; grav: %f\n", input_movevalues, maxspeed, friction, gravity);
#else
//printf("CLIENT: %v; max: %f; fric: %f; grav: %f\n", input_movevalues, maxspeed, friction, gravity);
#endif
/* give us a chance to manipulate input_ globals before running physics */
Physics_InputPreMove();
/* handle footsteps */
Footsteps_Update();
/* handle drowning and other environmental factors */
Physics_WaterMove();
/* grappling hook stuff, TODO: variable speeds */
if (grapvelocity != g_vec_null) {
velocity = (grapvelocity - origin);
velocity = (velocity * (1 / (vlen(velocity) / 750)));
}
Physics_SetViewParms();
Physics_Crouch();
Physics_Prone();
Physics_CheckJump(TRUE);
if (autocvar(pm_enginepmove, 0) > 0) {
/* fast engine-side player physics */
runstandardplayerphysics(this);
} else {
/* QuakeC powered physics (slow, but more customizable) */
PMoveCustom_RunPlayerPhysics(this);
}
Physics_CheckJump(FALSE);
if (wasOnGround == false && (flags & FL_ONGROUND)) {
if (waterlevel != 0) {
flFallVel = 0;
}
Physics_Fall(flFallVel - flBaseVel);
}
input_movevalues = saved_input_movevalues;
input_buttons = saved_input_buttons;
Physics_InputPostMove();
angles = fixAngle(angles);
#ifdef SERVER
/* Use Flagger */
vector src, dest;
makevectors(input_angles);
src = origin + view_ofs;
dest = src + v_forward * 64;
traceline(src, dest, MOVE_NORMAL, this);
RemoveVFlags(VFL_ONUSABLE);
if (trace_ent.identity == 1) {
ncEntity foo = (ncEntity)trace_ent;
if (foo.PlayerUse) {
AddVFlags(VFL_ONUSABLE);
}
}
if (XR_Available(this)) {
m_xrSpace.SetOrigin(origin);
m_xrSpace.SetAngles(input_angles);
} else {
m_xrSpace.SetOrigin(origin + view_ofs);
m_xrSpace.SetAngles(input_angles);
}
#endif
}