View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0006613 | The Dark Mod | Mapping | public | 23.03.2025 20:30 | 08.11.2026 18:42 |
| Reporter | stgatilov | Assigned To | stgatilov | ||
| Priority | normal | Severity | normal | Reproducibility | have not tried |
| Status | feedback | Resolution | open | ||
| Product Version | TDM 2.13 | ||||
| Target Version | TDM 2.15 | ||||
| Summary | 0006613: Swing mission: fix player launcher. | ||||
| Description | "Swing" platforming mission has a launcher pad, which is broken when uncapped FPS is on. The pad is supposed to launch the player with exactly predefined velocity. However, it is implemented using "mover" (like a door or elevator, basically), hence its behavior depends on physics and is quite non-deterministic with variable game tick. The idea is to modify the mission by setting player velocity at the moment of launch. | ||||
| Steps To Reproduce | 1) Start "Swing" 2) noclip 3) execute: teleport launcher 4) noclip (disable) 5) Hit the button on the launcher, 6) Make sure you fly where you are supposed to You can check where you are supposed to fly by disabling "Uncapped FPS". Make sure the destination makes sense and you can properly finish a mission =) | ||||
| Additional Information | Here is the entity: "classname" "atdm:mover_door_sliding" "name" "launcher" "accel_time" ".1" "decel_time" ".1" "translate" "-40.185 0 335" "translate_speed" "15000" Original report: https://forums.thedarkmod.com/index.php?/topic/22635-beta-testing-213/page/5/#findComment-500672 | ||||
| Tags | No tags attached. | ||||
|
Maybe I'll be called a heretic for this? I was goofing around in Google AI studio to see what it could do when given a tracker and our source code. The recommended changes look sane, seem to cover other possible issues, and don't seem to include any junk or hallucinations. Leaving here for an RFC ( or pitchforks and torches ? ) |
|
|
Physics_Player.cpp (191,995 bytes)
/*****************************************************************************
The Dark Mod GPL Source Code
This file is part of the The Dark Mod Source Code, originally based
on the Doom 3 GPL Source Code as published in 2011.
The Dark Mod 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 3 of the License,
or (at your option) any later version. For details, see LICENSE.TXT.
Project: The Dark Mod (http://www.thedarkmod.com/)
******************************************************************************/
#include "precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
#include "../DarkModGlobals.h"
#include "../Grabber.h"
#include "../BinaryFrobMover.h"
#include "../FrobDoor.h"
#include "Force_Push.h"
CLASS_DECLARATION( idPhysics_Actor, idPhysics_Player )
END_CLASS
// movement parameters
const float PM_STOPSPEED = 100.0f;
const float PM_SWIMSCALE = 1.0f;
const float PM_ROPESPEED = 100.0f;
const float PM_LADDERSPEED = 100.0f;
const float PM_STEPSCALE = 1.0f;
const float PM_ACCELERATE = 10.0f;
const float PM_AIRACCELERATE = 1.0f;
const float PM_WATERACCELERATE = 4.0f;
const float PM_FLYACCELERATE = 8.0f;
const float PM_FRICTION = 6.0f;
const float PM_AIRFRICTION = 0.0f;
const float PM_WATERFRICTION = 1.0f;
const float PM_FLYFRICTION = 3.0f;
const float PM_NOCLIPFRICTION = 12.0f;
const float PM_SLICK = 0.1f; // grayman - #2409 - for slippery surfaces
/**
* Friction multiplier to stop the player more quickly.
* Should also be tested with slopes as the gravity direction is always treated equally.
**/
const float PM_STOPFRICTIONMUL = 2.5f;
/**
* Low player speed boundary (squared), below which movement is fully stopped.
* Affects all directions except the current acceleration direction if there is acceleration.
**/
const float PM_MAXSTOPSPEEDSQR = 14.0f;
/**
* Height unit increment for mantle test
* This value should be >= 1.0
* A larger value reduces the number of tests during a mantle
* initiation, but may not find some small mantleable "nooks"
* in a surface.
**/
const float MANTLE_TEST_INCREMENT = 1.0;
/**
* Desired player speed below which ground friction is neglected
*
* This was determined for PM_FRICTION = 6.0 and should change if
* PM_FRICTION changes from 6.0.
**/
const float PM_NOFRICTION_SPEED = 71.0f;
const float MIN_WALK_NORMAL = 0.7f; // can't walk on very steep slopes (run = 1, rise = 1)
const float MIN_WALK_SLICK_NORMAL = 0.89f; // grayman #2409 - higher value for slippery slopes (run = 1, rise = 0.5)
const float OVERCLIP = 1.001f;
// TODO (ishtvan): Move the following to INI file or player def file:
/**
* Defines the spot above the player's origin where they are attached to the rope
**/
const float ROPE_GRABHEIGHT = 50.0f;
/**
* Distance the player is set back from the rope
**/
const float ROPE_DISTANCE = 20.0f;
/**
* Angular tolarance for looking at a rope and grabbing it [deg]
**/
const float ROPE_ATTACHANGLE = 45.0f*idMath::PI/180.0f;
/**
* Angular tolerance for when to start rotating around the rope
* (This one doesn't have to be in the def file)
**/
const float ROPE_ROTANG_TOL = 1.0f*idMath::PI/180.0f;
/**
* When moving on a climbable surface, the player's predicted position is checked by this delta ahead
* To make sure it's still on a ladder surface.
**/
const float CLIMB_SURFCHECK_DELTA = 5.0f;
/**
* How far to check from the player origin along the surface normal to hit the surface in the above test
* Needs to allow for worst case overhang
**/
const float CLIMB_SURFCHECK_NORMDELTA = 20.0f;
/**
* how far away is the player allowed to push out from the climbable section?
* Measured from the last good attachment point at the origin
**/
const float LADDER_DISTAWAY = 15.0f;
/**
* Velocity with which the player is shoved over the top of the ladder
* This depends on LADDER_DISTAWAY. If this velocity is too low, the player will
* fall down before they can make it over the top of the ladder
**/
const float LADDER_TOPVELOCITY = 80.0f;
/**
* Angle at which the player detaches from a ladder when their feet are walking on a surface
**/
const float LADDER_WALKDETACH_ANGLE = 45.0f;
const float LADDER_WALKDETACH_DOT = idMath::Cos( DEG2RAD( LADDER_WALKDETACH_ANGLE ) );
/**
* Player's eye needs to be closer than this in order to peek through an opening.
**/
const float PEEK_MAX_DIST = 22.0f; // grayman #4882
// movementFlags
const int PMF_DUCKED = 1; // set when ducking
const int PMF_JUMPED = 2; // set when the player jumped this frame
const int PMF_STEPPED_UP = 4; // set when the player stepped up this frame
const int PMF_STEPPED_DOWN = 8; // set when the player stepped down this frame
const int PMF_JUMP_HELD = 16; // set when jump button is held down
const int PMF_TIME_LAND = 32; // movementTime is time before rejump
const int PMF_TIME_KNOCKBACK = 64; // movementTime is an air-accelerate only time
const int PMF_TIME_WATERJUMP = 128; // movementTime is waterjump
const int PMF_ALL_TIMES = (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK);
int c_pmove = 0;
void idPhysics_Player::SetSelf( idEntity *e )
{
idPhysics_Base::SetSelf(e);
m_PushForce->SetOwner(e);
}
/*
============
idPhysics_Player::CmdScale
Returns the scale factor to apply to cmd movements
This allows the clients to use axial -127 to 127 values for all directions
without getting a sqrt(2) distortion in speed.
============
*/
float idPhysics_Player::CmdScale( const usercmd_t &cmd ) const {
int max;
float total;
float scale;
int forwardmove;
int rightmove;
int upmove;
forwardmove = cmd.forwardmove;
rightmove = cmd.rightmove;
// since the crouch key doubles as downward movement, ignore downward movement when we're on the ground
// otherwise crouch speed will be lower than specified
if ( walking ) {
upmove = 0;
} else {
upmove = cmd.upmove;
}
max = abs( forwardmove );
if ( abs( rightmove ) > max ) {
max = abs( rightmove );
}
if ( abs( upmove ) > max ) {
max = abs( upmove );
}
if ( !max ) {
return 0.0f;
}
total = idMath::Sqrt( (float) forwardmove * forwardmove + rightmove * rightmove + upmove * upmove );
scale = (float) playerSpeed * max / ( 127.0f * total );
return scale;
}
/*
==============
idPhysics_Player::Accelerate
Handles user intended acceleration
==============
*/
void idPhysics_Player::Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ) {
#if 1
// q2 style
//float addspeed;
float accelspeed, currentspeed;
currentspeed = current.velocity * wishdir;
/*addspeed = wishspeed - currentspeed;
if (addspeed <= 0) {
return;
}*/
// this acceleration has friction-like effects if currentspeed is bigger than wishspeed.
accelspeed = accel * frametime * (wishspeed - currentspeed); // accel * frametime * wishspeed;
/*if (accelspeed > addspeed) {
accelspeed = addspeed;
}*/
current.velocity += accelspeed * wishdir;
#else
// proper way (avoids strafe jump maxspeed bug), but feels bad
idVec3 wishVelocity;
idVec3 pushDir;
float pushLen;
float canPush;
wishVelocity = wishdir * wishspeed;
pushDir = wishVelocity - current.velocity;
pushLen = pushDir.Normalize();
canPush = accel * frametime * wishspeed;
if (canPush > pushLen) {
canPush = pushLen;
}
current.velocity += canPush * pushDir;
#endif
}
/*
==================
idPhysics_Player::SlideMove
Returns true if the velocity was clipped in some way
==================
*/
#define MAX_CLIP_PLANES 5
bool idPhysics_Player::SlideMove( bool gravity, bool stepUp, bool stepDown, bool push, const float velocityLimit /*= -1.0*/ ) {
int i, j, k, pushFlags;
int bumpcount, numbumps, numplanes;
float d, time_left, into;
float totalMass = 0.0f;
idVec3 dir, planes[MAX_CLIP_PLANES];
idVec3 end, stepEnd, primal_velocity, endVelocity, endClipVelocity, clipVelocity;
trace_t trace, stepTrace, downTrace;
bool nearGround, stepped, pushed;
numbumps = 4;
primal_velocity = current.velocity;
if ( gravity ) {
endVelocity = current.velocity + gravityVector * frametime;
current.velocity = ( current.velocity + endVelocity ) * 0.5f;
primal_velocity = endVelocity;
if ( groundPlane ) {
// slide along the ground plane
current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
}
}
else {
endVelocity = current.velocity;
}
time_left = frametime;
// never turn against the ground plane
if ( groundPlane ) {
numplanes = 1;
planes[0] = groundTrace.c.normal;
} else {
numplanes = 0;
}
// never turn against original velocity
planes[numplanes] = current.velocity;
planes[numplanes].Normalize();
numplanes++;
// BluePill : Push test tweaked for low frametimes (relies on hardcoded values)
// needs further testing whether it can negatively affect mission gameplay
if ( push && current.velocity.LengthSqr() > 0.0F )
{
// calculate the "real time" velocity as if the framerate was constantly 60fps
// idPhysics_RigidBody::Evaluate (-> idODE_*::Evaluate) already takes account of the frametime
// just applying current.velocity would cause the actual movement to be velocity*frametime^2 per frame, while it should be velocity*frametime.
idVec3 realtimeVelocity = current.velocity;
// prevent potential division by zero (minimum frametime should be 1ms)
if (frametime > 0.0001F)
realtimeVelocity *= (MS2SEC(USERCMD_MSEC) / frametime);
// BluePill : Hardcoded trace range of 5.0, looked good for different crouch/walk speeds
// calculate the end of the reachable range to push entities
end = current.origin + current.velocity * ( 5.0F / current.velocity.LengthFast() );
// see if we can make it there
gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
// if we are not blocked by the world
if ( trace.c.entityNum != ENTITYNUM_WORLD ) {
clipModel->SetPosition( current.origin, clipModel->GetAxis() );
// clip movement, only push idMoveables, don't push entities the player is standing on
// apply impact to pushed objects
pushFlags = PUSHFL_CLIP | PUSHFL_ONLYMOVEABLE | PUSHFL_NOGROUNDENTITIES | PUSHFL_APPLYIMPULSE;
// clip & push
// greebo: Don't use the idPusher
//totalMass = gameLocal.push.ClipTranslationalPush( trace, self, pushFlags, end, end - current.origin, cv_pm_pushmod.GetFloat() );
// Set the trace result to zero, we're pushing into things here
// trace.fraction = 0.0f;
trace.endpos = current.origin;
// greebo: Check the entity in front of us
idEntity* pushedEnt = gameLocal.entities[trace.c.entityNum];
if (pushedEnt != NULL)
{
// Register the blocking physics object with our push force
m_PushForce->SetPushEntity(pushedEnt, 0);
m_PushForce->SetContactInfo(trace, realtimeVelocity);
totalMass = pushedEnt->GetPhysics()->GetMass();
}
if (totalMass > 0.0f) {
// decrease velocity based on the total mass of the objects being pushed ?
current.velocity *= 1.0f - idMath::ClampFloat(0.0f, 1000.0f, totalMass - 20.0f) * (1.0f / 950.0f);
pushed = true;
}
}
}
for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ )
{
// calculate position we are trying to move to
end = current.origin + time_left * current.velocity;
// see if we can make it there
gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
time_left -= time_left * trace.fraction;
current.origin = trace.endpos;
// if moved the entire distance
if ( trace.fraction >= 1.0f )
{
break;
}
stepped = pushed = false;
// if we are allowed to step up
if ( stepUp )
{
nearGround = groundPlane || m_bOnClimb || m_bClimbDetachThisFrame;
if ( !nearGround )
{
// trace down to see if the player is near the ground
// step checking when near the ground allows the player to move up stairs smoothly while jumping
stepEnd = current.origin + maxStepHeight * gravityNormal;
gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
nearGround = ( downTrace.fraction < 1.0f && (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL );
}
// may only step up if near the ground or climbing
if ( nearGround )
{
// step up
stepEnd = current.origin - maxStepHeight * gravityNormal;
gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
// greebo: Trace along the current velocity, but trace further ahead than we need to judge the situation
idVec3 velocityNorm(current.velocity);
velocityNorm.NormalizeFast();
idVec3 fullForward = downTrace.endpos + maxStepHeight * velocityNorm;
gameLocal.clip.Translation( stepTrace, downTrace.endpos, fullForward, clipModel, clipModel->GetAxis(), clipMask, self );
// This is the max. distance we can move forward in this frame
idVec3 forward = time_left * current.velocity;
//float forwardDist = forward.LengthFast();
// step down
idVec3 topStartPoint = downTrace.endpos + forward;
stepEnd = topStartPoint + maxStepHeight * gravityNormal;
gameLocal.clip.Translation( downTrace, topStartPoint, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
if ( downTrace.fraction >= 1.0f || (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL )
{
// if moved the entire distance
if ( stepTrace.fraction >= 1.0f )
{
time_left = 0;
current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
current.origin = downTrace.endpos;
current.movementFlags |= PMF_STEPPED_UP;
current.velocity *= PM_STEPSCALE;
break;
}
// if the move is further when stepping up
if ( stepTrace.fraction > trace.fraction )
{
time_left -= time_left * stepTrace.fraction;
current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
current.origin = downTrace.endpos;
current.movementFlags |= PMF_STEPPED_UP;
current.velocity *= PM_STEPSCALE;
trace = stepTrace;
stepped = true;
}
}
else
{
// greebo: We have a sloped obstacle in front of us
if (stepTrace.fraction >= 1.0f)
{
// We can step onto this obstacle, the stepTrace has shown that there is enough room
// in front of us (if we translate a bit upwards)
current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
//static int factor = 10;
current.origin = downTrace.endpos + time_left*current.velocity; //(fullForward - current.origin)*stepTrace.fraction;
time_left = 0;
current.movementFlags |= PMF_STEPPED_UP;
current.velocity *= PM_STEPSCALE;
// greebo: HACK ALARM: We add a "counter-gravity" this frame to avoid us from being dragged down again in the next frame
// TODO: Maybe we can do this somewhere else, where the player is sliding off slopes.
current.velocity -= gravityVector * frametime * 3;
// BluePill: HACK to prevent sliding down curbs on high framerates.
m_SlopeIgnoreTimer = (framemsec < USERCMD_MSEC) ? 250 : 0;
}
}
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING ("performing step up, velocity now %.4f %.4f %.4f\r", current.velocity.x, current.velocity.y, current.velocity.z);
}
}
// original push test code; the new push test is located above this loop
/*// if we can push other entities and not blocked by the world
if ( push && trace.c.entityNum != ENTITYNUM_WORLD ) {
clipModel->SetPosition( current.origin, clipModel->GetAxis() );
// clip movement, only push idMoveables, don't push entities the player is standing on
// apply impact to pushed objects
pushFlags = PUSHFL_CLIP | PUSHFL_ONLYMOVEABLE | PUSHFL_NOGROUNDENTITIES | PUSHFL_APPLYIMPULSE;
// clip & push
// greebo: Don't use the idPusher
//totalMass = gameLocal.push.ClipTranslationalPush( trace, self, pushFlags, end, end - current.origin, cv_pm_pushmod.GetFloat() );
// Set the trace result to zero, we're pushing into things here
trace.fraction = 0.0f;
trace.endpos = current.origin;
// greebo: Check the entity in front of us
idEntity* pushedEnt = gameLocal.entities[trace.c.entityNum];
if (pushedEnt != NULL)
{
// Register the blocking physics object with our push force
m_PushForce->SetPushEntity(pushedEnt, 0);
m_PushForce->SetContactInfo(trace, current.velocity);
totalMass = pushedEnt->GetPhysics()->GetMass();
}
if ( totalMass > 0.0f ) {
// decrease velocity based on the total mass of the objects being pushed ?
current.velocity *= 1.0f - idMath::ClampFloat( 0.0f, 1000.0f, totalMass - 20.0f ) * ( 1.0f / 950.0f );
pushed = true;
}
current.origin = trace.endpos;
time_left -= time_left * trace.fraction;
// if moved the entire distance
if ( trace.fraction >= 1.0f ) {
break;
}
}*/
if ( !stepped ) {
// let the entity know about the collision
self->Collide( trace, current.velocity );
}
if ( numplanes >= MAX_CLIP_PLANES ) {
// MrElusive: I think we have some relatively high poly LWO models with a lot of slanted tris
// where it may hit the max clip planes
current.velocity = vec3_origin;
return true;
}
//
// if this is the same plane we hit before, nudge velocity
// out along it, which fixes some epsilon issues with
// non-axial planes
//
for ( i = 0; i < numplanes; i++ ) {
if ( ( trace.c.normal * planes[i] ) > 0.999f ) {
current.velocity += trace.c.normal;
break;
}
}
if ( i < numplanes ) {
continue;
}
planes[numplanes] = trace.c.normal;
numplanes++;
//
// modify velocity so it parallels all of the clip planes
//
// find a plane that it enters
for ( i = 0; i < numplanes; i++ ) {
into = current.velocity * planes[i];
if ( into >= 0.1f ) {
continue; // move doesn't interact with the plane
}
// slide along the plane
clipVelocity = current.velocity;
clipVelocity.ProjectOntoPlane( planes[i], OVERCLIP );
// slide along the plane
endClipVelocity = endVelocity;
endClipVelocity.ProjectOntoPlane( planes[i], OVERCLIP );
// see if there is a second plane that the new move enters
for ( j = 0; j < numplanes; j++ ) {
if ( j == i ) {
continue;
}
if ( ( clipVelocity * planes[j] ) >= 0.1f ) {
continue; // move doesn't interact with the plane
}
// try clipping the move to the plane
clipVelocity.ProjectOntoPlane( planes[j], OVERCLIP );
endClipVelocity.ProjectOntoPlane( planes[j], OVERCLIP );
// see if it goes back into the first clip plane
if ( ( clipVelocity * planes[i] ) >= 0 ) {
continue;
}
// slide the original velocity along the crease
dir = planes[i].Cross( planes[j] );
dir.Normalize();
d = dir * current.velocity;
clipVelocity = d * dir;
dir = planes[i].Cross( planes[j] );
dir.Normalize();
d = dir * endVelocity;
endClipVelocity = d * dir;
// see if there is a third plane the the new move enters
for ( k = 0; k < numplanes; k++ ) {
if ( k == i || k == j ) {
continue;
}
if ( ( clipVelocity * planes[k] ) >= 0.1f ) {
continue; // move doesn't interact with the plane
}
// stop dead at a tripple plane interaction
current.velocity = vec3_origin;
return true;
}
}
// if we have fixed all interactions, try another move
current.velocity = clipVelocity;
endVelocity = endClipVelocity;
break;
}
}
// step down
if ( stepDown && groundPlane )
{
stepEnd = current.origin + gravityNormal * maxStepHeight;
gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
if ( downTrace.fraction > 1e-4f && downTrace.fraction < 1.0f )
{
current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
current.origin = downTrace.endpos;
current.movementFlags |= PMF_STEPPED_DOWN;
current.velocity *= PM_STEPSCALE;
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING ("performing step down, velocity now %.4f %.4f %.4f\r", current.velocity.x, current.velocity.y, current.velocity.z);
}
}
if ( gravity ) {
current.velocity = endVelocity;
}
// come to a dead stop when the velocity orthogonal to the gravity flipped
clipVelocity = current.velocity - gravityNormal * current.velocity * gravityNormal;
endClipVelocity = endVelocity - gravityNormal * endVelocity * gravityNormal;
if ( clipVelocity * endClipVelocity < 0.0f ) {
current.velocity = gravityNormal * current.velocity * gravityNormal;
}
// Limit velocity
if (velocityLimit >= 0.0)
{
const float fSqrdVelocity = current.velocity.LengthSqr();
if (fSqrdVelocity > velocityLimit*velocityLimit)
{
current.velocity *= idMath::RSqrt(fSqrdVelocity) * velocityLimit;
}
}
return (bool)( bumpcount == 0 );
}
/*
==================
idPhysics_Player::Friction
Handles both ground friction and water friction
==================
*/
void idPhysics_Player::Friction( const idVec3 &wishdir, const float forceFriction )
{
idVec3 vel = current.velocity;
if ( walking ) {
// ignore slope movement, remove all velocity in gravity direction
vel -= (vel * gravityNormal) * gravityNormal;
}
/*
* stgatilov: this does not allow to start walking sometimes:
* https://forums.thedarkmod.com/index.php?/topic/22294-beta-testing-212/&do=findComment&comment=491835
* (all these snapping-like tweaks are bad in general, so nothing to lose here)
float speed = vel.Length();
if ( speed < 1.0f ) {
// remove all movement orthogonal to gravity, allows for sinking underwater
if ( fabs( current.velocity * gravityNormal ) < 1e-5f ) {
current.velocity.Zero();
} else {
current.velocity = (current.velocity * gravityNormal) * gravityNormal;
}
// greebo: We still want the player to slow down when reaching the surface
// This is where velocities are getting really small, but never actually reach 0,
// that's why this z-friction is necessary.
current.velocity.z *= cv_pm_water_z_friction.GetFloat();
return;
}
*/
// float drop = 0;
float friction = 0.0f;
if (forceFriction > 1e-3f) {
friction = forceFriction;
}
// spectator friction
else if ( current.movementType == PM_SPECTATOR ) {
// TODO if anyone is crazy enough to add multiplayer and spectator mode to TDM : Check whether this works as intented!
friction = PM_FLYFRICTION;
//drop += speed * PM_FLYFRICTION * frametime;
}
// apply ground friction
else if ( walking && waterLevel <= WATERLEVEL_FEET )
{
if ( !(current.movementFlags & PMF_TIME_KNOCKBACK) ) // if getting knocked back, no friction
{
// grayman - #2409 - less friction on slick surfaces
friction = PM_FRICTION; // default
if (groundMaterial && (groundMaterial->IsSlick()))
{
friction *= PM_SLICK; // reduce friction
}
/*float control = (speed < PM_STOPSPEED) ? PM_STOPSPEED : speed;
drop += control * friction * frametime;*/
}
}
// apply water friction even if just wading
else if (waterLevel) {
friction = PM_WATERFRICTION * waterLevel;
//drop += speed * PM_WATERFRICTION * waterLevel * frametime;
}
// apply air friction
else {
friction = PM_AIRFRICTION;
//drop += speed * PM_AIRFRICTION * frametime;
}
// if there is no player intended movement
if (wishdir.LengthSqr() <= 1e-5f) {
friction *= PM_STOPFRICTIONMUL;
}
// bluepill: don't apply friction to the current acceleration direction as the acceleration calculation does that already.
// don't set drop as this friction calculation doesn't treat all velocity components equally
idVec3 frictionComponent = vel - ((vel * wishdir) * wishdir);
/*if (frictionComponent.LengthSqr() <= PM_MAXSTOPSPEEDSQR) {
// fully stop movement for slow speeds
// stgatilov #6333: this causes player to hang on the top of jump with high FPS
// moreover, I'm not sure this is necessary... there is similar code in this function above
current.velocity -= frictionComponent;
}
else*/ {
current.velocity -= frictionComponent * (friction * frametime); // -1.0 for 100% frictionComponent
}
// scale the velocity
/*float newspeed = speed - drop;
if (newspeed < 0) {
newspeed = 0;
}
current.velocity *= ( newspeed / speed );*/
}
/*
===================
idPhysics_Player::WaterJumpMove
Flying out of the water
REMOVED from DarkMod
===================
*/
/*
===================
idPhysics_Player::WaterMove
===================
*/
void idPhysics_Player::WaterMove()
{
// Keep track of whether jump is held down for mantling out of water
if ( command.upmove > 10 )
{
current.movementFlags |= PMF_JUMP_HELD;
}
else
{
current.movementFlags &= ~PMF_JUMP_HELD;
}
// Lower ranged weapons while swimming
static_cast<idPlayer*>(self)->SetImmobilization( "WaterMove", EIM_ATTACK_RANGED );
//Friction();
float scale = CmdScale( command );
idVec3 wishvel;
// user intentions
if ( !scale ) {
// greebo: Standard downwards velocity is configurable via this CVAR
if (waterLevel >= WATERLEVEL_HEAD)
{
// greebo: Player is completely submersed, apply upwards velocity, but let it raise over time
float factor = (gameLocal.time - submerseTime) / 1000.0f;
if (factor > 1) {
factor = 1;
}
if (static_cast<idPlayer*>(self)->m_CrouchIntent)
{
// greebo: we probably have crouch mode activated
// Set the factor to 0, toggle crouch can here be used to keep the player at the current height
factor = 0;
}
// This makes the player slowly rise/sink in water
wishvel = gravityNormal * cv_pm_water_downwards_velocity.GetFloat() * factor;
}
else
{
wishvel.Zero();
}
} else {
// Regular swim speed
wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
wishvel -= scale * gravityNormal * command.upmove;
// stifu #3550: Simulate swimming motion via additive modulation of speed
// 1) Sinosodial lead-in
// 2) max-speed plateau
// 3) Sinosodial lead-out
// Sum of all phase portions has to be 1.0f
static const float fLeadInPortion = 0.4f;
static const float fLeadOutPortion = 0.2f;
assert(fLeadInPortion + fLeadOutPortion < 1.0f);
static const float fMaxSpeedPortion = 1.0f - fLeadInPortion - fLeadOutPortion;
assert(fMaxSpeedPortion >= 0.0f && fMaxSpeedPortion < 1.0f);
// (Re-)initialize members
if (cv_pm_swimspeed_frequency.IsModified() || cv_pm_swimspeed_variation.IsModified()
|| m_fSwimLeadInDuration_s < 0.0f || m_fSwimLeadOutStart_s < 0.0f
|| m_fSwimLeadOutDuration_s < 0.0f || m_fSwimSpeedModCompensation < 0.0f)
{
const float fFrequency = cv_pm_swimspeed_frequency.GetFloat();
m_fSwimLeadInDuration_s = fLeadInPortion / fFrequency;
const float fSwimMaxSpeedDuration_s = fMaxSpeedPortion / fFrequency;
m_fSwimLeadOutStart_s = m_fSwimLeadInDuration_s + fSwimMaxSpeedDuration_s;
m_fSwimLeadOutDuration_s = fLeadOutPortion / fFrequency;
// Max-speed plateau changes avg. speed -> Compensate that by subtracting
// a constant offset from the speed
for (int i = 0; i < 2; i++) // In case the first computation is invalid
{
const float fMaxSpeedPortionTotal = fSwimMaxSpeedDuration_s
/ (m_fSwimLeadInDuration_s + fSwimMaxSpeedDuration_s + m_fSwimLeadOutDuration_s);
m_fSwimSpeedModCompensation = 1.0f - cv_pm_swimspeed_variation.GetFloat() * fMaxSpeedPortionTotal;
// NOTE: cv_pm_swimspeed_variation could be incompatible with function.
// Try to correct the value in that case and recompute compensation
if (m_fSwimSpeedModCompensation - cv_pm_swimspeed_variation.GetFloat() < 0.0f)
cv_pm_swimspeed_variation.SetFloat(1/fMaxSpeedPortionTotal);
else
break;
}
cv_pm_swimspeed_frequency.ClearModified();
cv_pm_swimspeed_variation.ClearModified();
}
// Modulate swimming speed for the animation
if (m_fSwimTimeStart_s < m_fSwimLeadInDuration_s)
{
wishvel *= (m_fSwimSpeedModCompensation - cv_pm_swimspeed_variation.GetFloat()
* idMath::Cos(idMath::PI * m_fSwimTimeStart_s / m_fSwimLeadInDuration_s));
} else if (m_fSwimTimeStart_s < m_fSwimLeadOutStart_s)
{
wishvel *= (m_fSwimSpeedModCompensation + cv_pm_swimspeed_variation.GetFloat());
if (!m_bSwimSoundStarted)
PlaySwimBurstSound();
}
else if (m_fSwimTimeStart_s < (m_fSwimLeadOutStart_s + m_fSwimLeadOutDuration_s))
{
const float fTimeInLeadOut_s = m_fSwimTimeStart_s - m_fSwimLeadOutStart_s;
wishvel *= (m_fSwimSpeedModCompensation + cv_pm_swimspeed_variation.GetFloat()
* idMath::Cos(idMath::PI * fTimeInLeadOut_s / m_fSwimLeadOutDuration_s));
}
else
{
// Animation finished. Restart it!
m_fSwimTimeStart_s = -frametime;
m_bSwimSoundStarted = false;
}
m_fSwimTimeStart_s += frametime;
}
idVec3 wishdir = wishvel;
float wishspeed = wishdir.Normalize();
if ( wishspeed > playerSpeed * PM_SWIMSCALE ) {
wishspeed = playerSpeed * PM_SWIMSCALE;
}
idPhysics_Player::Accelerate( wishdir, wishspeed, PM_WATERACCELERATE );
// don't apply friction on the wishdir direction
idPhysics_Player::Friction( wishdir );
// make sure we can go up slopes easily under water
if ( groundPlane && ( current.velocity * groundTrace.c.normal ) < 0.0f ) {
float vel = current.velocity.Length();
// slide along the ground plane
current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
current.velocity.Normalize();
current.velocity *= vel;
}
idPhysics_Player::SlideMove( false, true, false, false );
}
void idPhysics_Player::PlaySwimBurstSound()
{
idStr sSound("snd_swim_burst");
idPlayer* pPlayer = static_cast<idPlayer*>(self);
if (pPlayer == nullptr)
return;
// speed mod
if (pPlayer->m_CreepIntent)
{
sSound += "_creep";
} else if (pPlayer->usercmd.buttons & BUTTON_RUN)
{
sSound += "_run";
}
else
{
sSound += "_walk";
}
// waterlevel mod
if (waterLevel >= WATERLEVEL_HEAD)
sSound += "_underwater";
pPlayer->StartSound(sSound, SND_CHANNEL_BODY, 0, false, nullptr);
m_bSwimSoundStarted = true;
}
/*
===================
idPhysics_Player::FlyMove
===================
*/
void idPhysics_Player::FlyMove( void ) {
idVec3 wishvel;
float wishspeed;
idVec3 wishdir;
float scale;
// normal slowdown
//idPhysics_Player::Friction();
scale = idPhysics_Player::CmdScale( command );
if ( !scale ) {
wishvel = vec3_origin;
} else {
wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
wishvel -= scale * gravityNormal * command.upmove;
}
wishdir = wishvel;
wishspeed = wishdir.Normalize();
idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE );
// don't apply friction on the wishdir direction
idPhysics_Player::Friction( wishdir );
idPhysics_Player::SlideMove( false, false, false, false );
}
/*
===================
idPhysics_Player::AirMove
===================
*/
void idPhysics_Player::AirMove( void ) {
idVec3 wishvel;
idVec3 wishdir;
float wishspeed;
float scale;
//idPhysics_Player::Friction();
scale = idPhysics_Player::CmdScale( command );
// project moves down to flat plane
viewForward -= (viewForward * gravityNormal) * gravityNormal;
viewRight -= (viewRight * gravityNormal) * gravityNormal;
viewForward.Normalize();
viewRight.Normalize();
wishvel = viewForward * command.forwardmove + viewRight * command.rightmove;
wishvel -= (wishvel * gravityNormal) * gravityNormal;
wishdir = wishvel;
wishspeed = wishdir.Normalize();
wishspeed *= scale;
// not on ground, so little effect on velocity
idPhysics_Player::Accelerate( wishdir, wishspeed, PM_AIRACCELERATE * m_AirAccelerate );
// don't apply friction on the wishdir direction
idPhysics_Player::Friction( wishdir );
// we may have a ground plane that is very steep, even
// though we don't have a groundentity
// slide along the steep plane
if ( groundPlane ) {
current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
}
idPhysics_Player::SlideMove( true, false, false, false );
m_bMidAir = true;
}
/*
===================
idPhysics_Player::WalkMove
===================
*/
void idPhysics_Player::WalkMove( void )
{
if ( waterLevel > WATERLEVEL_WAIST && (viewForward * groundTrace.c.normal) > 0.0f )
{
// begin swimming
WaterMove();
return;
}
if ( CheckJump() ) {
// jumped away
if ( waterLevel > WATERLEVEL_FEET ) {
WaterMove();
}
else {
AirMove();
}
return;
}
// BluePill : Move friction calculation after acceleration.
//Friction();
float scale = CmdScale( command );
// project moves down to flat plane
viewForward -= (viewForward * gravityNormal) * gravityNormal;
viewRight -= (viewRight * gravityNormal) * gravityNormal;
// project the forward and right directions onto the ground plane
viewForward.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
viewRight.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
//
viewForward.Normalize();
viewRight.Normalize();
idVec3 wishdir = viewForward * command.forwardmove + viewRight * command.rightmove;
float wishspeed = wishdir.Normalize();
wishspeed *= scale;
// clamp the speed lower if wading or walking on the bottom
if ( waterLevel )
{
float waterScale = waterLevel / 3.0f;
waterScale = 1.0f - ( 1.0f - PM_SWIMSCALE ) * waterScale;
if ( wishspeed > playerSpeed * waterScale ) {
wishspeed = playerSpeed * waterScale;
}
}
float accelerate = 0;
// when a player gets hit, they temporarily lose full control, which allows them to be moved a bit
if ( /*( groundMaterial && groundMaterial->IsSlick() ) || grayman #2409 */ current.movementFlags & PMF_TIME_KNOCKBACK ) {
accelerate = PM_AIRACCELERATE;
}
else
{
accelerate = PM_ACCELERATE;
//FIX: If the player is moving very slowly, bump up their acceleration
// so they don't get stuck to the floor by friction.
if( playerSpeed < PM_NOFRICTION_SPEED )
{
accelerate *= 3.0f;
}
}
Accelerate( wishdir, wishspeed, accelerate );
if ( /*( groundMaterial && groundMaterial->IsSlick() ) || grayman #2409 */ current.movementFlags & PMF_TIME_KNOCKBACK )
{
current.velocity += gravityVector * frametime;
}
// BluePill : don't apply friction on the wishdir direction, Accelerate does that already.
Friction( wishdir );
idVec3 oldVelocity = current.velocity;
// slide along the ground plane
current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
// if not clipped into the opposite direction
if ( oldVelocity * current.velocity > 0.0f )
{
float newVel = current.velocity.LengthSqr();
if ( newVel > 1.0f )
{
float oldVel = oldVelocity.LengthSqr();
if ( oldVel > 1.0f )
{
// don't decrease velocity when going up or down a slope
current.velocity *= idMath::Sqrt( oldVel / newVel );
}
}
}
// don't do anything if standing still
idVec3 vel = current.velocity - (current.velocity * gravityNormal) * gravityNormal;
if ( !vel.LengthSqr() )
{
// greebo: We're not moving, so let's clear the push entity
m_PushForce->SetPushEntity(NULL);
return;
}
gameLocal.push.InitSavingPushedEntityPositions();
SlideMove( false, true, true, true );
}
/*
==============
idPhysics_Player::DeadMove
==============
*/
void idPhysics_Player::DeadMove( void ) {
float forward;
if ( !walking ) {
return;
}
// extra friction
forward = current.velocity.Length();
forward -= 20;
if ( forward <= 0 ) {
current.velocity = vec3_origin;
}
else {
current.velocity.Normalize();
current.velocity *= forward;
}
}
/*
===============
idPhysics_Player::NoclipMove
===============
*/
void idPhysics_Player::NoclipMove( void ) {
//float speed, drop, friction, newspeed, stopspeed;
float scale, wishspeed;
idVec3 wishdir;
// friction
/*speed = current.velocity.Length();
if ( speed < 20.0f ) {
current.velocity = vec3_origin;
}
else {
stopspeed = playerSpeed * 0.3f;
if ( speed < stopspeed ) {
speed = stopspeed;
}
friction = PM_NOCLIPFRICTION;
drop = speed * friction * frametime;
// scale the velocity
newspeed = speed - drop;
if (newspeed < 0) {
newspeed = 0;
}
current.velocity *= newspeed / speed;
}*/
// accelerate
scale = idPhysics_Player::CmdScale( command );
wishdir = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
wishdir -= scale * gravityNormal * command.upmove;
wishspeed = wishdir.Normalize();
wishspeed *= scale;
idPhysics_Player::Accelerate( wishdir, wishspeed, PM_ACCELERATE );
// don't apply friction on the wishdir direction
idPhysics_Player::Friction( wishdir, PM_NOCLIPFRICTION );
// move
current.origin += frametime * current.velocity;
}
/*
===============
idPhysics_Player::SpectatorMove
===============
*/
void idPhysics_Player::SpectatorMove( void ) {
idVec3 wishvel;
float wishspeed;
idVec3 wishdir;
float scale;
// fly movement
// idPhysics_Player::Friction();
scale = idPhysics_Player::CmdScale( command );
if ( !scale ) {
wishvel = vec3_origin;
} else {
wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
}
wishdir = wishvel;
wishspeed = wishdir.Normalize();
idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE );
// don't apply friction on the wishdir direction
idPhysics_Player::Friction( wishdir );
idPhysics_Player::SlideMove( false, false, false, false );
}
/*
============
idPhysics_Player::RopeMove
============
*/
// TODO: This will probably reverse the up/down controls for upside-down ropes
// Would need some fix where desired direction of travel is flipped
// if climbDir * gravNormal is < 0
#pragma warning( disable : 4533 )
void idPhysics_Player::RopeMove( void )
{
idVec3 wishvel, ropeBodyOrig;
idVec3 ropePoint, offset, newOrigin, climbDir;
float wishspeed, scale, temp, deltaYaw, deltaAng1, deltaAng2;
float upscale, ropeTop, ropeBot; // z coordinates of the top and bottom of rope
idBounds ropeBounds;
trace_t transTrace; // used for clipping tests when moving the player
idVec3 transVec, forward, playerVel(0,0,0), PlayerPoint(0,0,0);
// shaft of rope in the rope coordinates, may need to be a spawnarg if rope models are inconsistent
idVec3 ropeShaft( 0.0f, 0.0f, 1.0f );
int bodID(0);
idPhysics_AF *ropePhys;
idPlayer *player = static_cast<idPlayer *>(self);
if( !m_RopeEntity.GetEntity() )
{
RopeDetach();
return; // early exit
}
ropePhys = static_cast<idPhysics_AF *>(m_RopeEntity.GetEntity()->GetPhysics());
// stick the player to the rope at an AF origin point closest to their arms
PlayerPoint = current.origin - gravityNormal*ROPE_GRABHEIGHT;
ropeBodyOrig = ropePhys->NearestBodyOrig( PlayerPoint, &bodID );
climbDir = ropePhys->GetAxis( bodID ) * ropeShaft;
// Find the rope attachment point, which now moves along the individual AF body axis
// Drawing a diagram, we find:
// (orig - ropeOrig) * gravNormal = | offset along rope axis | * (rope axis * gravNormal)
// Solve for offset along rope axis
idVec3 deltaPlayerBody = PlayerPoint - ropeBodyOrig;
// float offsetMag = (deltaPlayerBody * gravityNormal) / (climbDir * gravityNormal);
// angua: the other method got huge values for offsetMag at large angles
// between the climbDir and the gravity axis.
float offsetMag = deltaPlayerBody * climbDir;
ropePoint = ropeBodyOrig + offsetMag * climbDir;
// Uncomment for climb axis debugging
// gameRenderWorld->DebugArrow( colorRed, ropeBodyOrig, ropePoint, 1 );
SetRefEntVel( m_RopeEntity.GetEntity(), bodID );
// move the player velocity into the rope reference frame
current.velocity -= m_RefEntVelocity;
// store and then kill the player's transverse velocity
playerVel = current.velocity;
current.velocity = (current.velocity * climbDir) * climbDir;
// apply the player's weight to the AF body - COMMENTED OUT DUE TO AF CRAZINESS
// ropePhys->AddForce(bodID, ropePoint, mass * gravityVector );
// if the player has hit the rope this frame, apply an impulse based on their velocity
// pretend the deceleration takes place over a number of frames for realism (100 ms?)
if( m_bJustHitRope )
{
m_bJustHitRope = false;
// greebo: Apply an impulse on the entity the rope arrow is bound to
idEntity* ropeBindMaster = m_RopeEntity.GetEntity()->GetBindMaster();
if (ropeBindMaster != NULL)
{
idVec3 direction = GetGravityNormal();
idPhysics* bindMasterPhysics = ropeBindMaster->GetPhysics();
const idVec3& ropeOrigin = ropePhys->GetOrigin();
idAFBody* topMostBody = ropePhys->GetBody(0);
if (topMostBody != NULL)
{
// Correct the pull direction using the orientation of the topmost body.
//const idMat3& axis = topMostBody->GetWorldAxis();
direction = topMostBody->GetWorldAxis() * ropeShaft;
}
bindMasterPhysics->ApplyImpulse(0, ropeOrigin, direction * mass * cv_tdm_rope_pull_force_factor.GetFloat());
}
idVec3 vImpulse = playerVel - (playerVel * climbDir) * climbDir;
vImpulse *= mass;
// ishtvan fix: Always translational force, do not torque the rope body
// ropePhys->AddForce( bodID, ropePoint, vImpulse/0.1f );
ropePhys->AddForce( bodID, ropePhys->GetOrigin(bodID), vImpulse/0.1f, this );
}
// ======================== Rope Swinging =====================
if ( player->usercmd.buttons & BUTTON_ATTACK ) {
bool newKick = !(player->oldButtons & BUTTON_ATTACK) && (gameLocal.time - m_RopeKickTime) > cv_pm_rope_swing_reptime.GetInteger();
bool kickContinued = (gameLocal.time - m_RopeKickTime) < cv_pm_rope_swing_duration.GetInteger();
if (newKick || kickContinued) {
// default kick direction is forward
idVec3 kickDir = player->firstPersonViewAxis[0];
idVec3 bodyOrig = ropePhys->GetOrigin(bodID);
idMat3 rotDir = mat3_identity;
// apply modifiers if holding left/right/back
if( common->ButtonState(UB_MOVELEFT) )
{
rotDir = idAngles(0.0f, 90.0f, 0.0f).ToMat3();
}
else if( common->ButtonState(UB_MOVERIGHT) )
{
rotDir = idAngles(0.0f, 270.0f, 0.0f).ToMat3();
}
else if( common->ButtonState(UB_BACK) )
{
rotDir = idAngles(0.0f, 180.0f, 0.0f).ToMat3();
}
kickDir = rotDir * kickDir;
if (newKick) {
// do a trace to see if a solid is in the way, if so, kick off of it
trace_t trKick;
gameLocal.clip.TracePoint
(
trKick, bodyOrig,
bodyOrig + cv_pm_rope_swing_kickdist.GetFloat()*kickDir,
MASK_SOLID, self
);
if( trKick.fraction < 1.0f )
{
// reverse direction to kick off
kickDir *= -1.0f;
// apply reaction force to entity kicked (TODO: watch out for exploits)
idEntity *kickedEnt = gameLocal.entities[trKick.c.entityNum];
float kickMag = cv_pm_rope_swing_impulse.GetFloat();// / 25.0f; // divide by 25, it takes a lot to move AFs for some reason
kickedEnt->ApplyImpulse( self, trKick.c.id, trKick.c.point, -kickMag * kickDir );
}
// test: apply velocity to all bodies lower as well?
m_RopeKickTime = gameLocal.time;
}
// project to XY plane
kickDir -= GetGravityNormal() * (kickDir*GetGravityNormal());
kickDir.Normalize();
float force = cv_pm_rope_swing_impulse.GetFloat() / MS2SEC(cv_pm_rope_swing_duration.GetFloat());
ropePhys->AddForce( bodID, bodyOrig, kickDir * force, this );
}
}
// ==== Translate the player to the rope attachment point =====
offset = (current.origin - ropePoint);
offset.ProjectOntoPlane( -gravityNormal );
offset.Normalize();
offset *= ROPE_DISTANCE;
newOrigin = ropePoint + offset;
transVec = newOrigin - current.origin;
transVec -= (transVec * gravityNormal)*gravityNormal;
// check whether the player will clip anything, and only translate up to that point
ClipTranslation(transTrace, transVec, NULL);
newOrigin = current.origin + (transVec * transTrace.fraction);
Translate( newOrigin - current.origin );
// Find the top and bottom of the rope
// This must be done every frame since the rope may be deforming
ropeBounds = m_RopeEntity.GetEntity()->GetPhysics()->GetAbsBounds();
ropeTop = ropeBounds[0] * -gravityNormal;
ropeBot = ropeBounds[1] * -gravityNormal;
if( ropeTop < ropeBot )
{
// switch 'em
temp = ropeTop;
ropeTop = ropeBot;
ropeBot = temp;
}
// ============== read mouse input and orbit around the rope ===============
// recalculate offset because the player may have moved to stick point
offset = (ropePoint - current.origin);
offset.ProjectOntoPlane( -gravityNormal );
offset.Normalize();
// forward vector orthogonal to gravity
forward = viewForward - (gravityNormal * viewForward) * gravityNormal;
forward.Normalize();
deltaAng1 = offset * forward;
deltaYaw = m_DeltaViewYaw;
// use a different tolerance for rotating toward the rope vs away
// rotate forward by deltaAng to see if we are rotating towards or away from the rope
idRotation rotateView( vec3_origin, -gravityNormal, -deltaYaw );
rotateView.RotatePoint( forward );
deltaAng2 = offset * forward;
// only rotate around the rope if looking at the rope to within some angular tolerance
// always rotate if shifting view away
if( deltaAng1 >= idMath::Cos( ROPE_ROTANG_TOL )
|| ( (deltaAng2 < deltaAng1) && deltaAng1 > 0 ) )
{
newOrigin = current.origin;
// define the counter-rotation around the rope point using gravity axis
idRotation rotatePlayer( ropePoint, -gravityNormal, -deltaYaw );
rotatePlayer.RotatePoint( newOrigin );
// check whether the player will clip anything when orbiting
transVec = newOrigin - current.origin;
ClipTranslation(transTrace, transVec, NULL);
newOrigin = current.origin + (transVec * transTrace.fraction);
Translate( newOrigin - current.origin );
}
// ================ read control input for climbing movement ===============
upscale = (climbDir * viewForward - 0.5f) * 2.5f;
if ( upscale > 1.0f )
{
upscale = 1.0f;
}
else if ( upscale < -1.0f )
{
upscale = -1.0f;
}
scale = idPhysics_Player::CmdScale( command );
// up down movement
wishvel = 0.9f * climbDir * upscale * scale * (float)command.forwardmove;
/* greebo: Removed command.upmove portion from wishvel. I guess this was to support
the old crouching code which set command.upmove to negative values. This code
is no longer there so command.upmove is only non-zero during jumping,
which we can ignore here.
if ( command.upmove )
{
wishvel += 0.5f * climbDir * scale * (float) command.upmove;
}*/
// detach the player from the rope if they jump
if ( idPhysics_Player::CheckRopeJump())
{
RopeDetach();
goto Quit;
}
// if the player is above the top of the rope, don't climb up
// if the player is at the bottom of the rope, don't climb down
// subtract some amount to represent hanging on with arms above head
if (
(
wishvel * gravityNormal <= 0.0f
&& (((current.origin * -gravityNormal) + ROPE_GRABHEIGHT ) > ropeTop)
)
||
(
wishvel * gravityNormal >= 0.0f
&& (((current.origin * -gravityNormal) + ROPE_GRABHEIGHT + 35.0f) < ropeBot)
)
)
{
current.velocity -= (current.velocity * climbDir) * climbDir;
goto Quit;
}
// accelerate
wishspeed = wishvel.Normalize();
idPhysics_Player::Accelerate( wishvel, wishspeed, PM_ACCELERATE );
// cap the climb velocity
upscale = current.velocity * -climbDir;
if ( upscale < -PM_ROPESPEED )
{
current.velocity += climbDir * (upscale + PM_ROPESPEED);
}
else if ( upscale > PM_ROPESPEED )
{
current.velocity += climbDir * (upscale - PM_ROPESPEED);
}
// stop the player from sliding when they let go of the button
if ( (wishvel * gravityNormal) == 0.0f )
{
if ( current.velocity * gravityNormal < 0.0f )
{
current.velocity += gravityVector * frametime;
if ( current.velocity * gravityNormal > 0.0f )
{
current.velocity -= (gravityNormal * current.velocity) * gravityNormal;
}
}
else
{
current.velocity -= gravityVector * frametime;
if ( current.velocity * gravityNormal < 0.0f )
{
current.velocity -= (gravityNormal * current.velocity) * gravityNormal;
}
}
}
// If the player is climbing down and hits the ground, detach them from the rope
if ( (wishvel * gravityNormal) > 0.0f && groundPlane )
{
RopeDetach();
goto Quit;
}
// move the player velocity back into the world frame
current.velocity += m_RefEntVelocity;
// slide the player up and down with their calculated velocity
idPhysics_Player::SlideMove( false, ( command.forwardmove > 0 ), false, false );
Quit:
return;
}
#pragma warning( default : 4533 )
/*
============
idPhysics_Player::RopeDetach
============
*/
void idPhysics_Player::RopeDetach()
{
m_bOnRope = false;
// start the reattach timer
SetNextAttachTime(gameLocal.time + cv_tdm_reattach_delay.GetFloat());
static_cast<idPlayer*>(self)->SetImmobilization( "RopeMove", 0 );
// move the player velocity back into the world frame
current.velocity += m_RefEntVelocity;
// switch movement modes to the appropriate one
if ( waterLevel > WATERLEVEL_FEET )
{
WaterMove();
}
else
{
AirMove();
}
}
/*
============
idPhysics_Player::ClimbDetach
============
*/
void idPhysics_Player::ClimbDetach( bool bStepUp )
{
m_bOnClimb = false;
m_ClimbingOnEnt = NULL;
m_bClimbDetachThisFrame = true;
static_cast<idPlayer *>(self)->SetImmobilization("ClimbMove", 0);
current.velocity += m_RefEntVelocity;
// switch movement modes to the appropriate one
if( bStepUp )
{
// Step up at the top of a ladder
idVec3 ClimbNormXY = m_vClimbNormal - (gravityNormal * m_vClimbNormal) * gravityNormal;
ClimbNormXY.Normalize();
current.velocity += -ClimbNormXY * LADDER_TOPVELOCITY;
idPhysics_Player::SlideMove( false, true, false, true );
}
else if ( waterLevel > WATERLEVEL_FEET )
{
WaterMove();
}
else
{
AirMove();
}
SetNextAttachTime(gameLocal.time + cv_tdm_reattach_delay.GetFloat());
}
/*
============
idPhysics_Player::LadderMove
============
*/
void idPhysics_Player::LadderMove( void )
{
idVec3 wishdir( vec3_zero ), wishvel( vec3_zero ), right( vec3_zero );
idVec3 dir( vec3_zero ), start( vec3_zero ), end( vec3_zero ), delta( vec3_zero );
idVec3 AttachVel( vec3_zero ), RefFrameVel( vec3_zero );
idVec3 vReqVert( vec3_zero ), vReqHoriz( vec3_zero ), vHorizVect( vec3_zero );
float wishspeed(0.0f), scale(0.0f), accel(PM_ACCELERATE);
float upscale(0.0f), horizscale(0.0f), NormalDot(0.0f);
trace_t SurfTrace;
bool bMoveAllowed( true );
// jump off the climbable surface if they jump, or fall off if they hit crouch
// angua: detaching when hitting crouch is handled in idPlayer::PerformImpulse
if (idPhysics_Player::CheckRopeJump())
{
ClimbDetach();
return;
}
idVec3 ClimbNormXY = m_vClimbNormal - (m_vClimbNormal * gravityNormal) * gravityNormal;
ClimbNormXY.Normalize();
NormalDot = ClimbNormXY * viewForward;
// detach if their feet are on the ground walking away from the surface
if ( walking && -NormalDot * command.forwardmove < LADDER_WALKDETACH_DOT )
{
ClimbDetach();
return;
}
// ====================== stick to the ladder ========================
// Do a trace to figure out where to attach the player:
start = current.origin;
end = start - 48.0f * ClimbNormXY;
gameLocal.clip.Translation( SurfTrace, start, end, clipModel, clipModel->GetAxis(), clipMask, self );
// if there is a climbable surface in front of the player, stick to it
idEntity* testEnt = gameLocal.entities[SurfTrace.c.entityNum]; // grayman #2787
bool isVine = ( testEnt && testEnt->IsType( tdmVine::Type ) ); // grayman #2787
if ( ( SurfTrace.fraction != 1.0f ) &&
( ( SurfTrace.c.material && SurfTrace.c.material->IsLadder() ) || isVine ) ) // grayman #2787
{
// grayman #2787 - smooth out the end position if you struck a climbable vine piece.
// This fixes the choppy sideways movement on a vine.
idVec3 endPoint = SurfTrace.endpos;
idVec3 p = endPoint;
if ( isVine )
{
idVec3 vineOrigin = testEnt->GetPhysics()->GetOrigin();
idVec3 vinePeak = vineOrigin + 3.875 * ClimbNormXY; // 3.875 is the distance from the vine origin to its peak
float c = ( vinePeak - start ) * ClimbNormXY;
float e = -ClimbNormXY * ClimbNormXY;
idVec3 size = GetBounds().GetSize();
p = start - ( c/e - size.x/2 )*ClimbNormXY;
}
m_vClimbPoint = p + cv_pm_climb_distance.GetFloat() * ClimbNormXY;
// m_vClimbPoint = endPoint + cv_pm_climb_distance.GetFloat() * ClimbNormXY;
AttachVel = 10 * (m_vClimbPoint - current.origin);
// Now that we have a valid point, don't need to use the initial one
m_bClimbInitialPhase = false;
// Update sounds and movement speed caps for the surface if we change surfaces
idStr surfName = g_Global.GetSurfName(SurfTrace.c.material);
if (surfName != m_ClimbSurfName)
{
idStr LookUpName, TempStr;
const idKeyValue *kv = NULL;
m_ClimbSurfName = surfName;
LookUpName = "climb_max_speed_vert_";
TempStr = LookUpName + surfName;
if( ( kv = self->spawnArgs.FindKey(LookUpName.c_str()) ) != NULL )
m_ClimbMaxVelVert = atof( kv->GetValue().c_str() );
else
{
TempStr = LookUpName + "default";
m_ClimbMaxVelVert = self->spawnArgs.GetFloat( LookUpName.c_str(), "1.0" );
}
LookUpName = "climb_max_speed_horiz_";
TempStr = LookUpName + surfName;
if( ( kv = const_cast<idKeyValue *>( self->spawnArgs.FindKey(LookUpName.c_str())) ) != NULL )
m_ClimbMaxVelHoriz = atof( kv->GetValue().c_str() );
else
{
TempStr = LookUpName + "default";
m_ClimbMaxVelHoriz = self->spawnArgs.GetFloat( LookUpName.c_str(), "2.3" );
}
// sound repitition distances
LookUpName = "climb_snd_repdist_vert_";
TempStr = LookUpName + surfName;
if( ( kv = const_cast<idKeyValue *>( self->spawnArgs.FindKey(LookUpName.c_str())) ) != NULL )
m_ClimbSndRepDistVert = atoi( kv->GetValue().c_str() );
else
{
TempStr = LookUpName + "default";
m_ClimbSndRepDistVert = self->spawnArgs.GetInt( LookUpName.c_str(), "32" );
}
// sound repitition distances
LookUpName = "climb_snd_repdist_horiz_";
TempStr = LookUpName + surfName;
if( ( kv = const_cast<idKeyValue *>( self->spawnArgs.FindKey(LookUpName.c_str())) ) != NULL )
m_ClimbSndRepDistHoriz = atoi( kv->GetValue().c_str() );
else
{
TempStr = LookUpName + "default";
m_ClimbSndRepDistHoriz = self->spawnArgs.GetInt( LookUpName.c_str(), "32" );
}
}
}
else if( m_bClimbInitialPhase )
{
// We should already have m_vClimbPoint stored from the initial trace
AttachVel = 12.0f * (m_vClimbPoint - current.origin);
}
// stifu #4948: Do a ladder slide with non-damange terminal velocity
if (m_bSlideOrDetachClimb)
{
idPhysics_Player::SlideMove(true, false, false, false, cv_pm_ladderSlide_speedLimit.GetFloat());
return;
}
// TODO: Support non-rope climbable AFs by storing the AF body hit in the trace?
SetRefEntVel(m_ClimbingOnEnt.GetEntity());
// Move player into climbed on ent reference frame
current.velocity -= m_RefEntVelocity;
current.velocity = (gravityNormal * current.velocity) * gravityNormal + AttachVel;
scale = idPhysics_Player::CmdScale( command );
float lenVert = viewForward * -gravityNormal;
float lenTransv = idMath::Fabs(viewForward * gravityNormal.Cross(ClimbNormXY));
// Dump everything that's not in the transverse direction into the vertical direction
float lenVert2 = idMath::Sqrt(NormalDot * NormalDot + lenVert * lenVert);
// resolve up/down, with some tolerance so player can still go up looking slightly down
if( lenVert < -0.3 )
lenVert2 = -lenVert2;
vReqVert = lenVert2 * -gravityNormal * scale * (float)command.forwardmove;
vReqVert *= m_ClimbMaxVelVert;
// obtain the horizontal direction
vReqHoriz = viewForward - (ClimbNormXY * viewForward) * ClimbNormXY;
vReqHoriz -= (vReqHoriz * gravityNormal) * gravityNormal;
vReqHoriz.Normalize();
vReqHoriz *= lenTransv * scale * (float)command.forwardmove;
vReqHoriz *= m_ClimbMaxVelHoriz;
// Pure horizontal motion if looking close enough to horizontal:
if( lenTransv > 0.906 )
wishvel = vReqHoriz;
else
wishvel = vReqVert + vReqHoriz;
// strafe
if ( command.rightmove )
{
// right vector orthogonal to gravity
right = viewRight - (gravityNormal * viewRight) * gravityNormal;
// project right vector into ladder plane
right = right - (ClimbNormXY * right) * ClimbNormXY;
right.Normalize();
wishvel += m_ClimbMaxVelHoriz * right * scale * (float) command.rightmove;
}
// ========================== Surface Extent Test ======================
// This now just checks distance from the last valid climbing point
dir = wishvel;
dir.Normalize();
end = start + wishvel * frametime + dir * CLIMB_SURFCHECK_DELTA;
delta = m_vClimbPoint - end;
if( delta.LengthSqr() > LADDER_DISTAWAY * LADDER_DISTAWAY )
bMoveAllowed = false;
if( !bMoveAllowed )
{
// If we were trying to go up and reached the extent, attempt to step off the ladder
// Make sure we are really trying to go up, not first going off to the side and then up
// TODO: Tweak this delta.lengthsqr parameter of 25.0, only measure in the horizontal axis?
delta = current.origin - m_vClimbPoint;
delta -= (delta * gravityNormal) * gravityNormal;
if( NormalDot < 0.0f && -wishvel * gravityNormal > 0 && delta.LengthSqr() < 25.0f )
{
ClimbDetach( true );
return;
}
accel = idMath::INFINITY;
wishvel = vec3_zero;
}
// ========================== End Surface Extent Test ==================
// do strafe friction
// idPhysics_Player::Friction();
// accelerate
wishspeed = wishvel.Normalize();
idPhysics_Player::Accelerate( wishvel, wishspeed, accel );
// don't apply friction on the wishdir direction
idPhysics_Player::Friction( wishdir );
// cap the vertical travel velocity
upscale = current.velocity * -gravityNormal;
if ( upscale < -m_ClimbMaxVelVert * playerSpeed )
current.velocity += gravityNormal * (upscale + m_ClimbMaxVelVert * playerSpeed );
else if ( upscale > m_ClimbMaxVelVert * playerSpeed )
current.velocity += gravityNormal * (upscale - m_ClimbMaxVelVert * playerSpeed );
// cap the horizontal travel velocity
vHorizVect = current.velocity - (current.velocity * gravityNormal) * gravityNormal;
horizscale = vHorizVect.Normalize();
float horizDelta = horizscale;
horizscale = idMath::ClampFloat( -m_ClimbMaxVelHoriz * playerSpeed, m_ClimbMaxVelHoriz * playerSpeed, horizscale );
horizDelta -= horizscale;
current.velocity -= vHorizVect * horizDelta;
if ( (wishvel * gravityNormal) == 0.0f )
{
if ( current.velocity * gravityNormal < 0.0f )
{
current.velocity += gravityVector * frametime;
if ( current.velocity * gravityNormal > 0.0f )
{
current.velocity -= (gravityNormal * current.velocity) * gravityNormal;
}
}
else {
current.velocity -= gravityVector * frametime;
if ( current.velocity * gravityNormal < 0.0f )
{
current.velocity -= (gravityNormal * current.velocity) * gravityNormal;
}
}
}
// Move player velocity back into the world frame
current.velocity += m_RefEntVelocity;
idPhysics_Player::SlideMove( false, ( command.forwardmove > 0 ), false, false );
}
/*
=============
idPhysics_Player::CorrectAllSolid
=============
*/
void idPhysics_Player::CorrectAllSolid( trace_t &trace, int contents ) {
if ( debugLevel ) {
gameLocal.Printf( "%i:allsolid\n", c_pmove );
}
// SophisticatedZombie
//DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING ("performing CorrectAllSolid due to player inside solid object\n");
//
// Don't bump player up if they're standing in a previously picked up objects.
// This is complicated but because we want free object movement, we have to temporarily disable player clipping.
// But, if a players releases an object when they're inside it they float to the surface. By doing this check
// we can avoid that.
// ishtvan: Only use the "rising code" if we're still clipping something after a mantle has finished
if( m_mantlePhase == fixClipping_DarkModMantlePhase
&& !gameLocal.m_Grabber->HasClippedEntity() )
{
current.origin -= (GetGravityNormal() * 0.2f);
}
// FIXME: jitter around to find a free spot ?
if ( trace.fraction >= 1.0f ) {
memset( &trace, 0, sizeof( trace ) );
trace.endpos = current.origin;
trace.endAxis = clipModelAxis;
trace.fraction = 0.0f;
trace.c.dist = current.origin.z;
trace.c.normal.Set( 0, 0, 1 );
trace.c.point = current.origin;
trace.c.entityNum = ENTITYNUM_WORLD;
trace.c.id = 0;
trace.c.type = CONTACT_TRMVERTEX;
trace.c.material = NULL;
trace.c.contents = contents;
}
}
/*
=============
idPhysics_Player::CheckGround
=============
*/
void idPhysics_Player::CheckGround( void ) {
int i, contents;
bool hadGroundContacts;
hadGroundContacts = HasGroundContacts();
// set the clip model origin before getting the contacts
clipModel->SetPosition( current.origin, clipModel->GetAxis() );
EvaluateContacts();
// setup a ground trace from the contacts
groundTrace.endpos = current.origin;
groundTrace.endAxis = clipModel->GetAxis();
if ( contacts.Num() ) {
groundTrace.fraction = 0.0f;
groundTrace.c = contacts[0];
for ( i = 1; i < contacts.Num(); i++ ) {
groundTrace.c.normal += contacts[i].normal;
}
groundTrace.c.normal.Normalize();
} else {
groundTrace.fraction = 1.0f;
}
contents = gameLocal.clip.Contents( current.origin, clipModel, clipModel->GetAxis(), -1, self );
if ( contents & MASK_SOLID )
{
// do something corrective if stuck in solid
idPhysics_Player::CorrectAllSolid( groundTrace, contents );
}
//stgatilov: call hacky method CorrectAllSolid only once per mantle
//otherwise, player can start levitating up in a really unlucky case
/*else */if ( m_mantlePhase == fixClipping_DarkModMantlePhase )
{
// the mantle stage can advance to done if we're not currently clipping
m_mantlePhase = notMantling_DarkModMantlePhase;
}
// if the trace didn't hit anything, we are in free fall
if ( groundTrace.fraction == 1.0f )
{
groundPlane = false;
walking = false;
groundEntityPtr = NULL;
return;
}
groundMaterial = groundTrace.c.material;
// Store the ground entity
idEntity* groundEnt = gameLocal.entities[ groundTrace.c.entityNum ];
groundEntityPtr = groundEnt;
// check if getting thrown off the ground
// In uncapped mode (com_fixedTic 1), factor in pending pushVelocity so high-FPS ticks
// don't get clamped to the mover surface by WalkMove() before launch
idVec3 testVelocity = current.velocity;
if ( cvarSystem != NULL && cvarSystem->GetCVarBool( "com_fixedTic" ) ) {
testVelocity += current.pushVelocity;
}
if ( (testVelocity * -gravityNormal) > 0.0f && ( testVelocity * groundTrace.c.normal ) > 10.0f ) {
if ( debugLevel ) {
gameLocal.Printf( "%i:kickoff\n", gameLocal.time );
}
groundPlane = false;
walking = false;
groundEntityPtr = NULL;
return;
}
// grayman #2409 - apply velocity change due to friction loss on slick surfaces
bool slick = (groundMaterial && (groundMaterial->IsSlick()));
float walkNormal = MIN_WALK_NORMAL;
if (slick)
{
idVec3 velocityChange = groundTrace.c.normal;
velocityChange.z = 0; // no vertical component
current.velocity += 3*velocityChange;
walkNormal = MIN_WALK_SLICK_NORMAL;
}
// don't check for slopes for a short time (only used for high framerates)
if (m_SlopeIgnoreTimer <= 0) {
// slopes that are too steep will not be considered onground
if ( ( groundTrace.c.normal * -gravityNormal ) < walkNormal ) // grayman #2409
{
if ( debugLevel ) {
gameLocal.Printf( "%i:steep\n", c_pmove );
}
// FIXME: if they can't slide down the slope, let them walk (sharp crevices)
// make sure we don't die from sliding down a steep slope
if ( current.velocity * gravityNormal > 150.0f ) {
current.velocity -= ( current.velocity * gravityNormal - 150.0f ) * gravityNormal;
}
groundPlane = true;
walking = false;
return;
}
}
groundPlane = true;
walking = true;
// hitting solid ground will end a waterjump
if ( current.movementFlags & PMF_TIME_WATERJUMP ) {
current.movementFlags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND );
current.movementTime = 0;
}
// if the player didn't have ground contacts the previous frame
if ( !hadGroundContacts ) {
// greebo: Set the "jump" deadtime in any case to 100 msec to disallow jumps for small timespan
current.movementTime = 100;
// don't do landing time if we were just going down a slope
if ( (current.velocity * -gravityNormal) < -200.0f ) {
// don't allow another jump for a little while
current.movementFlags |= PMF_TIME_LAND;
current.movementTime = 100;
}
}
// let the entity know about the collision
self->Collide( groundTrace, current.velocity );
groundEnt = groundEntityPtr.GetEntity();
if ( groundTrace.c.entityNum != ENTITYNUM_WORLD && groundEnt != NULL )
{
idPhysics* groundPhysics = groundEnt->GetPhysics();
impactInfo_t info;
groundEnt->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info );
// greebo: Don't push entities that already have a velocity towards the ground.
if (groundPhysics != NULL && info.invMass != 0.0f &&
idMath::Fabs(groundPhysics->GetLinearVelocity()*gravityNormal) < VECTOR_EPSILON)
{
// greebo: Apply a force to the entity below the player
//gameRenderWorld->DebugArrow(colorCyan, current.origin, current.origin + gravityNormal*20, 1, 16);
groundPhysics->AddForce(0, current.origin, gravityNormal*mass*cv_pm_weightmod.GetFloat(), this);
}
}
}
/*
==============
idPhysics_Player::CheckDuck
Sets clip model size
==============
*/
void idPhysics_Player::CheckDuck( void ) {
float maxZ;
idPlayer* player = static_cast<idPlayer*>(self);
int oldMovementFlags = current.movementFlags;
bool idealCrouchState = player->GetIdealCrouchState();
if ( current.movementType == PM_DEAD ) {
maxZ = pm_deadheight.GetFloat();
} else {
// stand up when climbing a ladder or rope
if (idealCrouchState == true && !m_bOnClimb && !m_bOnRope)
{
if (waterLevel >= WATERLEVEL_WAIST)
{
// greebo: We're waist-deep in water, trace down a few units to see if we're standing on ground
trace_t trace;
idVec3 end = current.origin + gravityNormal * 20;
gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
if (trace.fraction < 1.0f)
{
// We're not floating in deep water, we're standing in waist-deep water, duck as requested.
current.movementFlags |= PMF_DUCKED;
}
}
else
{
// We're outside of water, just duck as requested
current.movementFlags |= PMF_DUCKED;
}
// greebo: Update the lean physics when crouching
UpdateLeanPhysics();
}
else if (!IsMantling() && !IsShouldering() && idealCrouchState == false) // MantleMod: SophisticatedZombie (DH): Don't stand up if crouch during mantle
{
// ideal crouch state is not negative anymore, check if we are still in crouch mode
// stand up if appropriate
if ( current.movementFlags & PMF_DUCKED )
{
bool canStandUp = true;
if (waterLevel >= WATERLEVEL_HEAD)
{
// greebo: We're still in water, check if there is ground below us so that we can stand up
trace_t trace;
float viewHeightDelta = pm_normalviewheight.GetFloat() - pm_crouchviewheight.GetFloat();
idVec3 end = current.origin + gravityNormal * viewHeightDelta;
gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
if (trace.fraction == 1.0f)
{
canStandUp = false; // trace missed, no ground below
}
else
{
// The ground trace hit a target, see if our eyes would emerge from the water
idVec3 pointAboveHead = player->GetEyePosition() - gravityNormal * viewHeightDelta * (1.0f - trace.fraction);
int contents = gameLocal.clip.Contents( pointAboveHead, NULL, mat3_identity, -1, self );
if (contents & MASK_WATER)
{
canStandUp = false; // The point above our head is in water
}
}
}
if (canStandUp)
{
// greebo: We're not in deep water, so try to stand up
idVec3 end = current.origin - ( pm_normalheight.GetFloat() - pm_crouchheight.GetFloat() ) * gravityNormal;
// Now perform the upwards trace to check if we have room to stand up
trace_t trace;
gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
if ( trace.fraction >= 1.0f ) {
// We have room above us, so turn off the crouch flag
current.movementFlags &= ~PMF_DUCKED;
}
}
}
}
if ( current.movementFlags & PMF_DUCKED )
{
// #5961 - Don't use crouch speed while climbing
if (!(OnRope() || OnLadder()))
{
playerSpeed = crouchSpeed;
}
maxZ = pm_crouchheight.GetFloat();
}
else
{
maxZ = pm_normalheight.GetFloat();
}
if (waterLevel == WATERLEVEL_HEAD)
{
// greebo: We're underwater, set the clipmodel to the crouched size
maxZ = pm_crouchheight.GetFloat();
// greebo: But still let the player swim as fast as if he was uncrouched
playerSpeed = walkSpeed;
}
// greebo: Check if we've submersed in a liquid. If yes: set the clipmodel to crouchheight
// And set the DUCKED flag to 1.
if (waterLevelChanged)
{
if (waterLevel == WATERLEVEL_HEAD)
{
// We've just submersed into water, set the model to ducked
current.movementFlags |= PMF_DUCKED;
// Translate the origin a bit upwards to prevent the player head from "jumping" downwards
SetOrigin(player->GetEyePosition() + gravityNormal * pm_crouchviewheight.GetFloat());
// Set the Eye height directly to the new value, to avoid the smoothing happening in idPlayer::Move()
player->SetEyeHeight(pm_crouchviewheight.GetFloat());
}
else if ((oldMovementFlags & PMF_DUCKED) && waterLevel == WATERLEVEL_WAIST && previousWaterLevel == WATERLEVEL_HEAD)
{
// angua: only stand up when we should not be crouching
if (idealCrouchState == false)
{
// We're just floating up to the water surface, switch back to non-crouch mode
// Clear the flag again, just to be sure
current.movementFlags &= ~PMF_DUCKED;
// greebo: Perform a trace to see how far we can move downwards
trace_t trace;
idVec3 end = player->GetEyePosition() + gravityNormal * pm_normalviewheight.GetFloat();
gameLocal.clip.Translation(trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self);
// Set the origin to the end position of the trace
SetOrigin(trace.endpos);
maxZ = pm_normalheight.GetFloat();
// Set the Eye height directly to the new value, to avoid the smoothing happening in idPlayer::Move()
player->SetEyeHeight(pm_normalviewheight.GetFloat());
}
}
}
// greebo: Update the lean physics when not crouching
UpdateLeanPhysics();
}
// if the clipModel height should change
if ( clipModel->GetBounds()[1][2] != maxZ )
{
idBounds bounds = clipModel->GetBounds();
bounds[1][2] = maxZ;
if ( pm_usecylinder.GetBool() ) {
clipModel->LoadModel( idTraceModel( bounds, 8 ) );
} else {
clipModel->LoadModel( idTraceModel( bounds ) );
}
}
}
/*
================
idPhysics_Player::CheckClimbable
DarkMod: Checks ropes, ladders and other climbables
================
*/
void idPhysics_Player::CheckClimbable( void )
{
if( current.movementTime )
return;
// if on the ground moving backwards
// TODO: This causes problems when rope-climbing down a steep incline
if( walking && command.forwardmove <= 0 )
return;
// Don't attach to ropes or ladders in the middle of a mantle
if ( IsMantling() )
return;
if ( m_bOnRope && m_bSlideOrDetachClimb )
return;
// stifu #4948: Continue checking when sliding vine and ladder. A non-damaging
// slide speed is only achieved when the player is looking at the climb.
// Otherwise, player will be detached.
/*
// Don't attach if we are holding an object in our hands
if( gameLocal.m_Grabber->GetSelected() != NULL )
goto Quit;
*/
// forward vector orthogonal to gravity
idVec3 forward = viewForward - (gravityNormal * viewForward) * gravityNormal;
forward.Normalize();
float tracedist;
if ( walking || ( waterLevel >= WATERLEVEL_WAIST ) )
{
// don't want to get sucked towards the ladder when still walking or when climbing
// nbohr1more; #625 prevent player from being sucked onto ladders from far away when underwater
tracedist = 1.0f;
}
else
{
tracedist = 48.0f;
}
idVec3 end = current.origin + tracedist * forward;
// modified to check contents_corpse to check for ropes
trace_t trace;
gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask | CONTENTS_CORPSE, self );
idVec3 delta;
float angleOff;
float dist;
// if near a surface
if ( trace.fraction < 1.0f )
{
idEntity* testEnt = gameLocal.entities[trace.c.entityNum];
// DarkMod: Check if we're looking at a rope and airborne
// TODO: Check the class type instead of the stringname, make new rope class
if ( testEnt && testEnt->m_bIsClimbableRope )
{
m_RopeEntTouched = static_cast<idAFEntity_Base *>(testEnt);
delta = (trace.c.point - current.origin);
delta = delta - (gravityNormal * delta) * gravityNormal;
dist = delta.LengthFast();
delta.Normalize();
angleOff = delta * forward;
// must be in the air to attach to the rope
// this is kind've a hack, but the rope has a different attach distance than the ladder
if(
!m_bOnRope
&& ( (trace.endpos - current.origin).Length() <= 2.0f )
&& !groundPlane
&& angleOff >= idMath::Cos( ROPE_ATTACHANGLE )
&& (testEnt != m_RopeEntity.GetEntity() || gameLocal.time > m_NextAttachTime)
)
{
// make sure rope segment is not touching the ground
int bodyID = m_RopeEntTouched.GetEntity()->BodyForClipModelId( trace.c.id );
if( !static_cast<idPhysics_AF *>(m_RopeEntTouched.GetEntity()->GetPhysics())->HasGroundContactsAtJoint( bodyID ) )
{
m_bRopeContact = true;
m_bJustHitRope = true;
m_RopeEntity = static_cast<idAFEntity_Base *>(testEnt);
return;
}
}
}
// if a climbable surface
// grayman #2787 - add a test for a climbable vine.
bool isVine = ( testEnt && testEnt->IsType( tdmVine::Type ) );
if ( ( ( trace.c.material && ( trace.c.material->IsLadder() ) ) || isVine )
&& ( gameLocal.time > m_NextAttachTime ) )
{
m_bClimbableAhead = true;
idVec3 vStickPoint = trace.endpos;
// check a step height higher
end = current.origin - gravityNormal * ( maxStepHeight * 0.75f );
gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
idVec3 start = trace.endpos;
end = start + tracedist * forward;
gameLocal.clip.Translation( trace, start, end, clipModel, clipModel->GetAxis(), clipMask, self );
// if also near a surface a step height higher
if ( trace.fraction < 1.0f )
{
// if it also is a ladder surface
// grayman #2787 - add a test for a climbable vine
idEntity* testEntHigher = gameLocal.entities[trace.c.entityNum]; // grayman #2787
bool isVineHigher = ( testEntHigher && testEntHigher->IsType( tdmVine::Type ) );
if ( ( trace.c.material && trace.c.material->IsLadder() ) || isVineHigher ) // grayman #2787
{
m_vClimbNormal = trace.c.normal;
if ( isVineHigher ) // grayman #2787 - if climbing a vine, flatten out the normal
{
m_vClimbNormal = testEntHigher->GetPhysics()->GetAxis().ToAngles().ToForward();
}
m_ClimbingOnEnt = testEntHigher;
// Initial climbing attachment
// FIX: Used to get stuck hovering in some cases, now there's an initial phase
if ( !m_bOnClimb )
{
m_bClimbInitialPhase = true;
m_vClimbPoint = vStickPoint;
static_cast<idPlayer *>(self)->SetImmobilization( "ClimbMove", EIM_ATTACK );
}
m_bOnClimb = true;
return;
}
}
}
}
if (!m_bClimbableAhead && m_bOnClimb && m_bSlideOrDetachClimb)
{
// Not facing towards climbable surface. Cancel slide.
ClimbDetach();
m_bSlideOrDetachClimb = false;
}
// Rope attachment failsafe: Check intersection with the rope as well
if
(
!m_bOnRope
&& m_RopeEntTouched.GetEntity() != NULL
&& m_RopeEntTouched.GetEntity()->GetPhysics()->GetAbsBounds().IntersectsBounds( self->GetPhysics()->GetAbsBounds() )
&& !groundPlane
)
{
// test distance against the nearest rope body
int touchedBody = -1;
idVec3 PlayerPoint = current.origin + -gravityNormal*ROPE_GRABHEIGHT;
idVec3 RopeSegPoint = static_cast<idPhysics_AF *>(m_RopeEntTouched.GetEntity()->GetPhysics())->NearestBodyOrig( PlayerPoint, &touchedBody );
delta = ( RopeSegPoint - PlayerPoint);
delta = delta - (gravityNormal * delta) * gravityNormal;
dist = delta.LengthFast();
delta.Normalize();
angleOff = delta * forward;
// if the player is looking high up, override the angle check
float lookUpAng = viewForward * -gravityNormal;
// set lookup to true if the player is looking 60 deg up or more
bool bLookingUp = lookUpAng >= idMath::Cos(idMath::PI/6);
if
(
dist <= ROPE_DISTANCE
&& ( angleOff >= idMath::Cos( ROPE_ATTACHANGLE ) || bLookingUp )
&& (m_RopeEntTouched.GetEntity() != m_RopeEntity.GetEntity() || gameLocal.time > m_NextAttachTime)
&& !static_cast<idPhysics_AF *>(m_RopeEntTouched.GetEntity()->GetPhysics())->HasGroundContactsAtJoint( touchedBody )
)
{
m_bRopeContact = true;
m_bJustHitRope = true;
m_RopeEntity = m_RopeEntTouched.GetEntity();
return;
}
}
}
/*
=============
idPhysics_Player::CheckJump
=============
*/
bool idPhysics_Player::CheckJump()
{
if ( command.upmove < 10 )
{
// not holding jump
return false;
}
// must wait for jump to be released or dead time to have passed
if ( current.movementFlags & PMF_JUMP_HELD || current.movementTime > 0)
{
return false;
}
groundPlane = false; // jumping away
walking = false;
current.movementFlags |= PMF_JUMP_HELD | PMF_JUMPED;
idVec3 extraSpeedForward = viewForward - (gravityNormal * viewForward) * gravityNormal;
extraSpeedForward.Normalize();
// back paddling ?
if ( command.forwardmove < 0 )
{
// greebo: Apply a modifier when doing backwards jumps
extraSpeedForward = -extraSpeedForward * cv_tdm_backwards_jump_modifier.GetFloat();
}
// strafing right?
if ( command.rightmove > 0)
{
extraSpeedForward = viewRight - (gravityNormal * viewRight) * gravityNormal;
extraSpeedForward.Normalize();
}
// strafing left?
else if ( command.rightmove < 0 )
{
extraSpeedForward = viewRight - (gravityNormal * viewRight) * gravityNormal;
extraSpeedForward = -extraSpeedForward;
extraSpeedForward.Normalize();
}
idVec3 addVelocity;
float curVelocity = current.velocity.LengthFast();
// are we walking?
if ( curVelocity >= cv_tdm_min_vel_jump.GetFloat() &&
curVelocity >= pm_walkspeed.GetFloat() &&
curVelocity < ( pm_walkspeed.GetFloat() * cv_pm_runmod.GetFloat() ) )
{
addVelocity = cv_tdm_walk_jump_vel.GetFloat() * maxJumpHeight * -gravityVector;
extraSpeedForward *= cv_tdm_fwd_jump_vel.GetFloat();
}
// running ?
else if ( curVelocity >= cv_tdm_min_vel_jump.GetFloat()
&& curVelocity >= cv_pm_runmod.GetFloat())
{
addVelocity = cv_tdm_run_jump_vel.GetFloat() * maxJumpHeight * -gravityVector;
extraSpeedForward *= cv_tdm_fwd_jump_vel.GetFloat();
}
// stationary
else
{
addVelocity = 2.0f * maxJumpHeight * -gravityVector;
}
// angua: reduce jump velocity when crouching, unless we are on a ladder or rope
// cv_tdm_crouch_jump_vel can also be set to 0 to disable jumping while crouching
if ( current.movementFlags & PMF_DUCKED && !OnRope() && !OnLadder())
{
addVelocity *= cv_tdm_crouch_jump_vel.GetFloat();
extraSpeedForward *= cv_tdm_crouch_jump_vel.GetFloat();
}
if (addVelocity.LengthFast() > 0)
{
addVelocity *= idMath::Sqrt( addVelocity.Normalize() );
}
// greebo: Consider jump stamina
float jumpStaminaFactor = 1.0f;
if (lastJumpTime > -1)
{
int timeSinceLastJump = gameLocal.time - lastJumpTime;
float jumpRelaxationTime = SEC2MS(cv_tdm_jump_relaxation_time.GetFloat());
float factor = timeSinceLastJump * timeSinceLastJump / (jumpRelaxationTime * jumpRelaxationTime);
jumpStaminaFactor = idMath::ClampFloat(0.2f, 1, factor);
}
current.velocity += addVelocity + extraSpeedForward * jumpStaminaFactor;
// Remember the last jump time
if (jumpStaminaFactor > 0.1f)
{
lastJumpTime = gameLocal.time;
}
return true;
}
/*
=============
idPhysics_Player::CheckRopeJump
=============
*/
bool idPhysics_Player::CheckRopeJump( void )
{
if ( command.upmove < 10 ) {
// not holding jump
return false;
}
// must wait for jump to be released
if ( current.movementFlags & PMF_JUMP_HELD ) {
return false;
}
// angua: should be able to jump on a rope or ladder, even when crouched
// don't jump if we can't stand up
// if ( current.movementFlags & PMF_DUCKED ) {
// return false;
// }
groundPlane = false; // jumping away
walking = false;
current.movementFlags |= PMF_JUMP_HELD | PMF_JUMPED;
// the jump direction is an equal sum of up and the direction we're looking
idVec3 jumpDir = viewForward - gravityNormal;
jumpDir *= 1.0f/idMath::Sqrt(2.0f);
// TODO: Make this an adjustable cvar, currently too high?
idVec3 addVelocity = 2.0f * maxJumpHeight * gravityVector.Length() * jumpDir;
addVelocity *= idMath::Sqrt( addVelocity.Normalize() );
current.velocity += addVelocity;
return true;
}
/*
=============
idPhysics_Player::CheckWaterJump
REMOVED from DarkMod
=============
*/
/*
=============
idPhysics_Player::SetWaterLevel
For MOD_WATERPHYSICS this is moved to Physics_Actor.cpp
=============
*/
#ifndef MOD_WATERPHYSICS
void idPhysics_Player::SetWaterLevel( void ) {
idVec3 point;
idBounds bounds;
int contents;
//
// get waterlevel, accounting for ducking
//
waterLevel = WATERLEVEL_NONE;
waterType = 0;
bounds = clipModel->GetBounds();
// check at feet level
point = current.origin - ( bounds[0][2] + 1.0f ) * gravityNormal;
contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self );
if ( contents & MASK_WATER ) {
waterType = contents;
waterLevel = WATERLEVEL_FEET;
// check at waist level
point = current.origin - ( bounds[1][2] - bounds[0][2] ) * 0.5f * gravityNormal;
contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self );
if ( contents & MASK_WATER ) {
waterLevel = WATERLEVEL_WAIST;
// check at head level
point = current.origin - ( bounds[1][2] - 1.0f ) * gravityNormal;
contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self );
if ( contents & MASK_WATER ) {
waterLevel = WATERLEVEL_HEAD;
}
}
}
}
#endif
/*
================
idPhysics_Player::DropTimers
================
*/
void idPhysics_Player::DropTimers( void ) {
// drop misc timing counter
if ( current.movementTime ) {
if ( framemsec >= current.movementTime ) {
current.movementFlags &= ~PMF_ALL_TIMES;
current.movementTime = 0;
}
else {
current.movementTime -= framemsec;
}
}
}
/*
================
idPhysics_Player::MovePlayer
================
*/
void idPhysics_Player::MovePlayer( int msec ) {
// determine the time
framemsec = msec;
frametime = framemsec * 0.001f;
// this counter lets us debug movement problems with a journal
// by setting a conditional breakpoint for the previous frame
c_pmove++;
walking = false;
groundPlane = false;
m_bRopeContact = false;
m_bClimbableAhead = false;
m_bClimbDetachThisFrame = false;
m_bMidAir = false;
// default speed
playerSpeed = walkSpeed;
// remove jumped and stepped up flag
current.movementFlags &= ~(PMF_JUMPED|PMF_STEPPED_UP|PMF_STEPPED_DOWN);
current.stepUp = 0.0f;
// update the slope ignore timer
if (m_SlopeIgnoreTimer > 0)
m_SlopeIgnoreTimer -= framemsec;
if ( command.upmove < 10 ) {
// not holding jump
current.movementFlags &= ~PMF_JUMP_HELD;
if (m_mantlePhase == notMantling_DarkModMantlePhase
|| m_mantlePhase == fixClipping_DarkModMantlePhase)
{
// greebo: Jump button is released and no mantle phase is active,
// we can allow the next mantling process.
m_mantleStartPossible = true;
}
}
// if no movement at all
if ( current.movementType == PM_FREEZE ) {
return;
}
// move the player velocity into the frame of a pusher
current.velocity -= current.pushVelocity;
// view vectors
viewAngles.ToVectors( &viewForward, NULL, NULL );
viewForward *= clipModelAxis;
viewRight = gravityNormal.Cross( viewForward );
viewRight.Normalize();
// fly in spectator mode
if ( current.movementType == PM_SPECTATOR ) {
SpectatorMove();
idPhysics_Player::DropTimers();
return;
}
// special no clip mode
if ( current.movementType == PM_NOCLIP ) {
idPhysics_Player::NoclipMove();
idPhysics_Player::DropTimers();
return;
}
// no control when dead
if ( current.movementType == PM_DEAD ) {
command.forwardmove = 0;
command.rightmove = 0;
command.upmove = 0;
}
// set watertype and waterlevel
idPhysics_Player::SetWaterLevel(true); // greebo: Update the previousWaterLevel here
// check for ground
idPhysics_Player::CheckGround();
// check if a ladder or a rope is straight ahead
idPhysics_Player::CheckClimbable();
// set clip model size
idPhysics_Player::CheckDuck();
// handle timers
idPhysics_Player::DropTimers();
// Mantle Mod: SophisticatdZombie (DH)
idPhysics_Player::UpdateMantleTimers();
// Lean Mod: Zaccheus and SophisticatedZombie (DH)
idPhysics_Player::LeanMove();
// Check if holding down jump
if (CheckJumpHeldDown())
{
PerformMantle();
}
// move
if ( current.movementType == PM_DEAD ) {
// dead
DeadMove();
}
else if (m_eShoulderAnimState == eShoulderingAnimation_Active)
{
// Shouldering viewport animation
ShoulderingMove();
}
// continue moving on the rope if still attached
else if ( m_bOnRope )
{
// Check rope movement and let go if the rope is moving too fast
float maxRopeVelocity = cv_pm_rope_velocity_letgo.GetFloat();
idEntity* ropeEntity = m_RopeEntity.GetEntity();
if (ropeEntity != NULL)
{
float ropeVelocity = ropeEntity->/*GetBindMaster()->*/GetPhysics()->GetLinearVelocity().z;
//gameLocal.Printf("Rope Velocity: %f\n", m_RopeEntity.GetEntity()->GetBindMaster()->GetPhysics()->GetLinearVelocity().LengthFast());
if (ropeVelocity > maxRopeVelocity)
{
gameLocal.Printf("Rope is too fast (%f)! Letting go...\n", ropeVelocity);
RopeDetach();
}
}
RopeMove();
}
// Mantle MOD
// SophisticatedZombie (DH)
// greebo: Do the MantleMove before checking the rope contacts
else if ( !(m_mantlePhase == notMantling_DarkModMantlePhase || m_mantlePhase == fixClipping_DarkModMantlePhase) )
{
MantleMove();
}
else if ( m_bRopeContact )
{
// toggle m_bOnRope
m_bOnRope = true;
// lower weapon
static_cast<idPlayer*>(self)->SetImmobilization( "RopeMove", EIM_ATTACK | EIM_ITEM_DROP );
RopeMove();
}
else if ( m_bOnClimb )
{
// going up or down a ladder
LadderMove();
}
else if ( waterLevel > WATERLEVEL_FEET )
{
// swimming
WaterMove();
}
else if ( walking ) {
// walking on ground
WalkMove();
}
else {
// airborne
AirMove();
}
if (waterLevel <= WATERLEVEL_FEET && m_fSwimTimeStart_s != 0.0f)
// Reset swimming animation timer
m_fSwimTimeStart_s = 0.0f;
if (m_eShoulderAnimState == eShoulderingAnimation_Scheduled)
// Try to start shouldering animation
StartShoulderingAnim();
// enable weapon if not swimming
if ( ( waterLevel <= WATERLEVEL_FEET ) && static_cast<idPlayer*>(self)->GetImmobilization("WaterMove") && walking ) // grayman #3413
{
static_cast<idPlayer*>(self)->SetImmobilization("WaterMove", 0);
}
// set watertype, waterlevel and groundentity
SetWaterLevel(false); // greebo: Don't update the previousWaterLevel this time
CheckGround();
// move the player velocity back into the world frame
current.velocity += current.pushVelocity;
current.pushVelocity.Zero();
// DEBUG
/*
gameRenderWorld->DebugBounds
(
idVec4 (1.0, 0.0, 1.0, 1.0),
clipModel->GetAbsBounds(),
idVec3 (0.0, 0.0, 0.0)
);
*/
m_lastCommandViewYaw = command.angles[1];
m_lastCommandViewPitch = command.angles[0];
}
#ifndef MOD_WATERPHYSICS
/*
================
idPhysics_Player::GetWaterLevel
For MOD_WATERPHYSICS this is moved to Physics_Actor.cpp
================
*/
waterLevel_t idPhysics_Player::GetWaterLevel( void ) const {
return waterLevel;
}
/*
================
idPhysics_Player::GetWaterType
For MOD_WATERPHYSICS this is moved to Physics_Actor.cpp
================
*/
int idPhysics_Player::GetWaterType( void ) const {
return waterType;
}
#endif
/*
================
idPhysics_Player::HasJumped
================
*/
bool idPhysics_Player::HasJumped( void ) const {
return ( ( current.movementFlags & PMF_JUMPED ) != 0 && current.movementTime <= 0);
}
/*
================
idPhysics_Player::HasSteppedUp
================
*/
bool idPhysics_Player::HasSteppedUp( void ) const {
return ( ( current.movementFlags & ( PMF_STEPPED_UP | PMF_STEPPED_DOWN ) ) != 0 );
}
/*
================
idPhysics_Player::GetStepUp
================
*/
float idPhysics_Player::GetStepUp( void ) const {
return current.stepUp;
}
/*
================
idPhysics_Player::IsCrouching
================
*/
bool idPhysics_Player::IsCrouching( void ) const {
return ( ( current.movementFlags & PMF_DUCKED ) != 0 );
}
/*
================
idPhysics_Player::IsHangMantle
================
*/
bool idPhysics_Player::IsHangMantle( void ) const {
return m_mantlePhase == hang_DarkModMantlePhase;
}
/*
================
idPhysics_Player::IsPullMantle
================
*/
bool idPhysics_Player::IsPullMantle( void ) const {
return m_mantlePhase == pull_DarkModMantlePhase;
}
/*
================
idPhysics_Player::GetLastJumpTime
================
*/
int idPhysics_Player::GetLastJumpTime() const {
return lastJumpTime;
}
idEntity* idPhysics_Player::GetRopeEntity()
{
return (m_bOnRope) ? m_RopeEntity.GetEntity() : NULL;
}
/*
================
idPhysics_Player::OnRope
================
*/
bool idPhysics_Player::OnRope( void ) const
{
return m_bOnRope;
}
/*
================
idPhysics_Player::OnLadder
================
*/
bool idPhysics_Player::OnLadder( void ) const {
return m_bOnClimb;
}
/*
================
idPhysics_Player::idPhysics_Player
================
*/
idPhysics_Player::idPhysics_Player( void )
: m_eShoulderAnimState(eShoulderingAnimation_NotStarted)
, m_fShoulderingTime(0.0f)
, m_bShouldering_SkipDucking(false)
, m_fShouldering_TimeToNextSound(0.0f)
, m_bMidAir(false)
, m_fPrevShoulderingPitchOffset(0.0f)
, m_PrevShoulderingPosOffset(vec3_zero)
, m_ShoulderingStartPosRelative(vec3_zero)
, m_ShoulderingCurrentPosRelative(vec3_zero)
, m_pShoulderingGroundEntity(NULL)
, m_fSwimTimeStart_s(0.0f)
, m_fSwimLeadInDuration_s(-1.0f)
, m_fSwimLeadOutStart_s(-1.0f)
, m_fSwimLeadOutDuration_s(-1.0f)
, m_fSwimSpeedModCompensation(-1.0f)
, m_bSwimSoundStarted(false)
, m_mantlePullStartPos(vec3_zero)
, m_mantlePullEndPos(vec3_zero)
, m_mantlePushEndPos(vec3_zero)
, m_mantleCancelStartRoll(0.0f)
, m_fmantleCancelDist(0.0f)
, m_mantleCancelStartPos(vec3_zero)
, m_mantleCancelEndPos(vec3_zero)
, m_mantleStartPosWorld(vec3_zero)
{
debugLevel = false;
clipModel = NULL;
clipMask = 0;
memset( ¤t, 0, sizeof( current ) );
saved = current;
walkSpeed = 0;
crouchSpeed = 0;
maxStepHeight = 0;
maxJumpHeight = 0;
m_AirAccelerate = 1.0f;
memset( &command, 0, sizeof( command ) );
lastJumpTime = -1;
viewAngles.Zero();
framemsec = 0;
frametime = 0;
playerSpeed = 0;
viewForward.Zero();
viewRight.Zero();
walking = false;
groundPlane = false;
memset( &groundTrace, 0, sizeof( groundTrace ) );
groundMaterial = NULL;
m_RefEntVelocity.Zero();
// rope climbing
m_bRopeContact = false;
m_bOnRope = false;
m_bJustHitRope = false;
m_RopeEntity = NULL;
m_RopeEntTouched = NULL;
m_RopeKickTime = 0;
// wall/ladder climbing
m_bClimbableAhead = false;
m_bOnClimb = false;
m_bClimbDetachThisFrame = false;
m_bClimbInitialPhase = false;
m_vClimbNormal.Zero();
m_vClimbPoint.Zero();
m_ClimbingOnEnt = NULL;
m_ClimbSurfName.Clear();
m_ClimbMaxVelHoriz = 0.0f;
m_ClimbMaxVelVert = 0.0f;
m_ClimbSndRepDistVert = 0;
m_ClimbSndRepDistHoriz = 0;
m_bSlideOrDetachClimb = false;
m_bSlideInitialized = false;
m_NextAttachTime = -1;
m_PushForce = CForcePushPtr(new CForcePush);
m_SlopeIgnoreTimer = 0;
// swimming
waterLevel = WATERLEVEL_NONE;
waterType = 0;
// Mantle Mod
m_mantlePhase = notMantling_DarkModMantlePhase;
m_mantleTime = 0.0;
m_p_mantledEntity = NULL;
m_mantledEntityID = 0;
m_jumpHeldDownTime = 0.0;
m_mantleStartPossible = true;
// Leaning Mod
m_leanYawAngleDegrees = 0.0;
m_CurrentLeanTiltDegrees = 0.0;
m_b_leanFinished = true;
m_leanTime = 0.0f;
m_leanMoveStartTilt = 0.0;
m_leanMoveEndTilt = 0.0;
m_leanMoveMaxAngle = 0.0;
m_lastCommandViewYaw = 0;
m_lastCommandViewPitch = 0;
m_viewLeanAngles = ang_zero;
m_viewLeanTranslation = vec3_zero;
m_LeanListenPos = vec3_zero;
m_LeanEnt = NULL;
m_DeltaViewYaw = 0.0;
m_DeltaViewPitch = 0.0;
// Initialize lean view bounds used for collision
m_LeanViewBounds.Zero();
m_LeanViewBounds.ExpandSelf( 4.0f );
// bounds extend downwards so that player can't lean over very high ledges
idVec3 lowerPoint(0,0,-15.0f);
m_LeanViewBounds.AddPoint( lowerPoint );
}
/*
================
idPhysics_Player_SavePState
================
*/
void idPhysics_Player_SavePState( idSaveGame *savefile, const playerPState_t &state ) {
savefile->WriteVec3( state.origin );
savefile->WriteVec3( state.velocity );
savefile->WriteVec3( state.localOrigin );
savefile->WriteVec3( state.pushVelocity );
savefile->WriteFloat( state.stepUp );
savefile->WriteInt( state.movementType );
savefile->WriteInt( state.movementFlags );
savefile->WriteInt( state.movementTime );
}
/*
================
idPhysics_Player_RestorePState
================
*/
void idPhysics_Player_RestorePState( idRestoreGame *savefile, playerPState_t &state ) {
savefile->ReadVec3( state.origin );
savefile->ReadVec3( state.velocity );
savefile->ReadVec3( state.localOrigin );
savefile->ReadVec3( state.pushVelocity );
savefile->ReadFloat( state.stepUp );
savefile->ReadInt( state.movementType );
savefile->ReadInt( state.movementFlags );
savefile->ReadInt( state.movementTime );
}
/*
================
idPhysics_Player::Save
================
*/
void idPhysics_Player::Save( idSaveGame *savefile ) const {
idPhysics_Player_SavePState( savefile, current );
idPhysics_Player_SavePState( savefile, saved );
savefile->WriteFloat( walkSpeed );
savefile->WriteFloat( crouchSpeed );
savefile->WriteFloat( maxStepHeight );
savefile->WriteFloat( maxJumpHeight );
savefile->WriteFloat( m_AirAccelerate );
savefile->WriteInt( debugLevel );
savefile->WriteInt(lastJumpTime);
savefile->WriteUsercmd( command );
savefile->WriteAngles( viewAngles );
savefile->WriteInt( framemsec );
savefile->WriteFloat( frametime );
savefile->WriteFloat( playerSpeed );
savefile->WriteVec3( viewForward );
savefile->WriteVec3( viewRight );
savefile->WriteBool( walking );
savefile->WriteBool( groundPlane );
savefile->WriteTrace( groundTrace );
savefile->WriteMaterial( groundMaterial );
savefile->WriteVec3( m_RefEntVelocity );
savefile->WriteBool( m_bRopeContact );
savefile->WriteBool( m_bJustHitRope );
savefile->WriteBool( m_bOnRope );
savefile->WriteInt( m_RopeKickTime );
m_RopeEntity.Save( savefile );
m_RopeEntTouched.Save( savefile );
savefile->WriteBool( m_bClimbableAhead );
savefile->WriteBool( m_bOnClimb );
savefile->WriteBool( m_bClimbDetachThisFrame );
savefile->WriteBool( m_bClimbInitialPhase );
savefile->WriteVec3( m_vClimbNormal );
savefile->WriteVec3( m_vClimbPoint );
savefile->WriteString( m_ClimbSurfName.c_str() );
savefile->WriteFloat( m_ClimbMaxVelHoriz );
savefile->WriteFloat( m_ClimbMaxVelVert );
savefile->WriteInt( m_ClimbSndRepDistVert );
savefile->WriteInt( m_ClimbSndRepDistHoriz );
m_ClimbingOnEnt.Save( savefile );
savefile->WriteInt( m_NextAttachTime );
savefile->WriteInt( (int)waterLevel );
savefile->WriteInt( waterType );
// Mantle mod
savefile->WriteInt(m_mantlePhase);
savefile->WriteBool(m_mantleStartPossible);
savefile->WriteVec3(m_mantlePullStartPos);
savefile->WriteVec3(m_mantlePullEndPos);
savefile->WriteVec3(m_mantlePushEndPos);
savefile->WriteObject(m_p_mantledEntity);
savefile->WriteInt(m_mantledEntityID);
savefile->WriteFloat(m_mantleTime);
savefile->WriteFloat(m_jumpHeldDownTime);
// Mantle cancel animation
savefile->WriteFloat(m_mantleCancelStartRoll);
savefile->WriteFloat(m_fmantleCancelDist);
savefile->WriteVec3(m_mantleCancelStartPos);
savefile->WriteVec3(m_mantleCancelEndPos);
savefile->WriteVec3(m_mantleStartPosWorld);
// Lean mod
savefile->WriteFloat (m_leanYawAngleDegrees);
savefile->WriteFloat (m_CurrentLeanTiltDegrees);
savefile->WriteFloat (m_leanMoveStartTilt);
savefile->WriteFloat (m_leanMoveEndTilt);
savefile->WriteFloat (m_leanMoveMaxAngle);
savefile->WriteBool (m_b_leanFinished);
savefile->WriteFloat (m_leanTime);
savefile->WriteAngles (m_lastPlayerViewAngles);
savefile->WriteAngles (m_viewLeanAngles);
savefile->WriteVec3 (m_viewLeanTranslation);
savefile->WriteVec3 (m_LeanListenPos);
m_LeanEnt.Save( savefile );
savefile->WriteStaticObject(*m_PushForce);
savefile->WriteBool(m_bSlideInitialized);
// Shouldering anim
savefile->WriteInt(m_eShoulderAnimState);
savefile->WriteFloat(m_fShoulderingTime);
savefile->WriteVec3(m_PrevShoulderingPosOffset);
savefile->WriteVec3(m_ShoulderingStartPosRelative);
savefile->WriteVec3(m_ShoulderingCurrentPosRelative);
savefile->WriteObject(m_pShoulderingGroundEntity);
savefile->WriteBool(m_bShouldering_SkipDucking);
savefile->WriteFloat(m_fShouldering_TimeToNextSound);
savefile->WriteFloat(m_fPrevShoulderingPitchOffset);
savefile->WriteBool(m_bMidAir);
// Swimming
savefile->WriteFloat(m_fSwimTimeStart_s);
savefile->WriteBool(m_bSwimSoundStarted);
}
/*
================
idPhysics_Player::Restore
================
*/
void idPhysics_Player::Restore( idRestoreGame *savefile ) {
idPhysics_Player_RestorePState( savefile, current );
idPhysics_Player_RestorePState( savefile, saved );
savefile->ReadFloat( walkSpeed );
savefile->ReadFloat( crouchSpeed );
savefile->ReadFloat( maxStepHeight );
savefile->ReadFloat( maxJumpHeight );
savefile->ReadFloat( m_AirAccelerate );
savefile->ReadInt( debugLevel );
savefile->ReadInt(lastJumpTime);
savefile->ReadUsercmd( command );
savefile->ReadAngles( viewAngles );
savefile->ReadInt( framemsec );
savefile->ReadFloat( frametime );
savefile->ReadFloat( playerSpeed );
savefile->ReadVec3( viewForward );
savefile->ReadVec3( viewRight );
savefile->ReadBool( walking );
savefile->ReadBool( groundPlane );
savefile->ReadTrace( groundTrace );
savefile->ReadMaterial( groundMaterial );
savefile->ReadVec3( m_RefEntVelocity );
savefile->ReadBool( m_bRopeContact );
savefile->ReadBool( m_bJustHitRope );
savefile->ReadBool( m_bOnRope );
savefile->ReadInt( m_RopeKickTime );
m_RopeEntity.Restore( savefile );
m_RopeEntTouched.Restore( savefile );
// Angle storage vars need to be reset on a restore, since D3 resets the command angle to 0
m_lastCommandViewYaw = 0.0f;
m_lastCommandViewPitch = 0.0f;
m_DeltaViewYaw = 0.0f;
m_DeltaViewPitch = 0.0f;
savefile->ReadBool( m_bClimbableAhead );
savefile->ReadBool( m_bOnClimb );
savefile->ReadBool( m_bClimbDetachThisFrame );
savefile->ReadBool( m_bClimbInitialPhase );
savefile->ReadVec3( m_vClimbNormal );
savefile->ReadVec3( m_vClimbPoint );
savefile->ReadString( m_ClimbSurfName );
savefile->ReadFloat( m_ClimbMaxVelHoriz );
savefile->ReadFloat( m_ClimbMaxVelVert );
savefile->ReadInt( m_ClimbSndRepDistVert );
savefile->ReadInt( m_ClimbSndRepDistHoriz );
m_ClimbingOnEnt.Restore( savefile );
savefile->ReadInt( m_NextAttachTime );
m_SlopeIgnoreTimer = 0;
savefile->ReadInt( (int &)waterLevel );
savefile->ReadInt( waterType );
/* DG: It can apparently happen that the player saves while the clipModel's axis are
* modified by idPush::TryRotatePushEntity() -> idPhysics_Player::Rotate() -> idClipModel::Link()
* Normally idPush seems to reset them to the identity matrix in the next frame,
* but apparently not when coming from a savegame.
* Usually clipModel->axis is the identity matrix, and if it isn't there's clipping bugs
* like CheckGround() reporting that it's steep even though the player is only trying to
* walk up normal stairs.
* Resetting the axis to mat3_identity when restoring a savegame works around that issue
* and makes sure players can go on playing if their savegame was "corrupted" by saving
* while idPush was active. See https://github.com/dhewm/dhewm3/issues/328 for more details */
if ( clipModel != nullptr ) {
clipModel->SetPosition( clipModel->GetOrigin(), mat3_identity );
}
// Mantle mod
{
int temp;
savefile->ReadInt(temp);
assert(temp >= 0 && temp < NumMantlePhases); // sanity check
m_mantlePhase = static_cast<EMantlePhase>(temp);
}
savefile->ReadBool(m_mantleStartPossible);
savefile->ReadVec3(m_mantlePullStartPos);
savefile->ReadVec3(m_mantlePullEndPos);
savefile->ReadVec3(m_mantlePushEndPos);
savefile->ReadObject(reinterpret_cast<idClass*&>(m_p_mantledEntity));
savefile->ReadInt(m_mantledEntityID);
savefile->ReadFloat(m_mantleTime);
savefile->ReadFloat(m_jumpHeldDownTime);
// Mantle Cancel animation
savefile->ReadFloat(m_mantleCancelStartRoll);
savefile->ReadFloat(m_fmantleCancelDist);
savefile->ReadVec3(m_mantleCancelStartPos);
savefile->ReadVec3(m_mantleCancelEndPos);
savefile->ReadVec3(m_mantleStartPosWorld);
// Lean mod
savefile->ReadFloat (m_leanYawAngleDegrees);
savefile->ReadFloat (m_CurrentLeanTiltDegrees);
savefile->ReadFloat (m_leanMoveStartTilt);
savefile->ReadFloat (m_leanMoveEndTilt);
savefile->ReadFloat (m_leanMoveMaxAngle);
savefile->ReadBool (m_b_leanFinished);
savefile->ReadFloat (m_leanTime);
savefile->ReadAngles (m_lastPlayerViewAngles);
savefile->ReadAngles (m_viewLeanAngles);
savefile->ReadVec3 (m_viewLeanTranslation);
savefile->ReadVec3 (m_LeanListenPos);
m_LeanEnt.Restore( savefile );
// Cancel all leaning on load
if (m_CurrentLeanTiltDegrees > 0.0f)
{
m_leanMoveStartTilt = m_CurrentLeanTiltDegrees;
m_leanTime = 0.0f;
m_b_leanFinished = false;
m_leanMoveEndTilt = 0.0f;
}
savefile->ReadStaticObject( *m_PushForce );
// ishtvan: To avoid accidental latching, clear held crouch key var
m_bSlideOrDetachClimb = false;
savefile->ReadBool(m_bSlideInitialized);
// Shouldering anim
{
int iSAS = 0;
savefile->ReadInt(iSAS);
assert(iSAS >= eShoulderingAnimation_NotStarted
&& iSAS <= eShoulderingAnimation_Active); // sanity check
m_eShoulderAnimState = static_cast<eShoulderingAnimation>(iSAS);
}
savefile->ReadFloat(m_fShoulderingTime);
savefile->ReadVec3(m_PrevShoulderingPosOffset);
savefile->ReadVec3(m_ShoulderingStartPosRelative);
savefile->ReadVec3(m_ShoulderingCurrentPosRelative);
savefile->ReadObject(reinterpret_cast<idClass*&>(m_pShoulderingGroundEntity));
savefile->ReadBool(m_bShouldering_SkipDucking);
savefile->ReadFloat(m_fShouldering_TimeToNextSound);
savefile->ReadFloat(m_fPrevShoulderingPitchOffset);
savefile->ReadBool(m_bMidAir);
// Swimming
savefile->ReadFloat(m_fSwimTimeStart_s);
savefile->ReadBool(m_bSwimSoundStarted);
m_fSwimLeadInDuration_s = -1.0f;
m_fSwimLeadOutStart_s = -1.0f;
m_fSwimLeadOutDuration_s = -1.0f;
m_fSwimSpeedModCompensation = -1.0f;
DM_LOG (LC_MOVEMENT, LT_DEBUG)LOGSTRING ("Restore finished\n");
}
/*
================
idPhysics_Player::SetPlayerInput
================
*/
void idPhysics_Player::SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles )
{
command = cmd;
m_DeltaViewYaw = command.angles[1] - m_lastCommandViewYaw;
m_DeltaViewPitch = command.angles[0] - m_lastCommandViewPitch;
m_DeltaViewYaw = SHORT2ANGLE(m_DeltaViewYaw);
m_DeltaViewPitch = SHORT2ANGLE(m_DeltaViewPitch);
// Test for pitch clamping
float TestPitch = viewAngles.pitch + m_DeltaViewPitch;
TestPitch = idMath::AngleNormalize180( TestPitch );
if( TestPitch > pm_maxviewpitch.GetFloat() )
m_DeltaViewPitch = pm_maxviewpitch.GetFloat() - viewAngles.pitch;
else if( TestPitch < pm_minviewpitch.GetFloat() )
m_DeltaViewPitch = pm_minviewpitch.GetFloat() - viewAngles.pitch;
// zero the delta angles if the player's view is locked in place
if( static_cast<idPlayer *>(self)->GetImmobilization() & EIM_VIEW_ANGLE )
{
m_DeltaViewYaw = 0.0f;
m_DeltaViewPitch = 0.0f;
}
viewAngles = newViewAngles; // can't use cmd.angles cause of the delta_angles
m_lastCommandViewYaw = command.angles[1];
m_lastCommandViewPitch = command.angles[0];
}
/*
================
idPhysics_Player::SetSpeed
================
*/
void idPhysics_Player::SetSpeed( const float newWalkSpeed, const float newCrouchSpeed )
{
walkSpeed = newWalkSpeed;
crouchSpeed = newCrouchSpeed;
}
/*
================
idPhysics_Player::SetMaxStepHeight
================
*/
void idPhysics_Player::SetMaxStepHeight( const float newMaxStepHeight ) {
maxStepHeight = newMaxStepHeight;
}
/*
================
idPhysics_Player::GetMaxStepHeight
================
*/
float idPhysics_Player::GetMaxStepHeight( void ) const {
return maxStepHeight;
}
/*
================
idPhysics_Player::SetMaxJumpHeight
================
*/
void idPhysics_Player::SetMaxJumpHeight( const float newMaxJumpHeight ) {
maxJumpHeight = newMaxJumpHeight;
}
/*
================
idPhysics_Player::SetMovementType
================
*/
void idPhysics_Player::SetMovementType( const pmtype_t type ) {
current.movementType = type;
}
/*
================
idPhysics_Player::GetMovementType - grayman #2345
================
*/
int idPhysics_Player::GetMovementType( void ) {
return current.movementType;
}
/*
================
idPhysics_Player::SetAirAccelerate
================
*/
void idPhysics_Player::SetAirAccelerate( const float newAirAccelerate )
{
m_AirAccelerate = newAirAccelerate;
}
/*
================
idPhysics_Player::SetKnockBack
================
*/
void idPhysics_Player::SetKnockBack( const int knockBackTime ) {
if ( current.movementTime ) {
return;
}
current.movementFlags |= PMF_TIME_KNOCKBACK;
current.movementTime = knockBackTime;
}
/*
================
idPhysics_Player::SetDebugLevel
================
*/
void idPhysics_Player::SetDebugLevel( bool set ) {
debugLevel = set;
}
/*
================
idPhysics_Player::Evaluate
================
*/
bool idPhysics_Player::Evaluate( int timeStepMSec, int endTimeMSec ) {
idVec3 masterOrigin, oldOrigin;
idMat3 masterAxis;
// greebo: Don't clear the WATERLEVELs each frame, they are updated anyway.
//waterLevel = WATERLEVEL_NONE;
//waterType = 0;
oldOrigin = current.origin;
clipModel->Unlink();
// if bound to a master
if ( masterEntity )
{
self->GetMasterPosition( masterOrigin, masterAxis );
current.origin = masterOrigin + current.localOrigin * masterAxis;
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
current.velocity = ( current.origin - oldOrigin ) / ( timeStepMSec * 0.001f );
masterDeltaYaw = masterYaw;
masterYaw = masterAxis[0].ToYaw();
masterDeltaYaw = masterYaw - masterDeltaYaw;
return true;
}
ActivateContactEntities();
MovePlayer( timeStepMSec );
// Apply the push force to all objects encountered during MovePlayer
m_PushForce->Evaluate( endTimeMSec );
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
if ( IsOutsideWorld() ) {
gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) );
}
return true; //( current.origin != oldOrigin );
}
/*
================
idPhysics_Player::UpdateTime
================
*/
void idPhysics_Player::UpdateTime( int endTimeMSec ) {
}
/*
================
idPhysics_Player::GetTime
================
*/
int idPhysics_Player::GetTime( void ) const {
return gameLocal.time;
}
/*
================
idPhysics_Player::GetImpactInfo
================
*/
void idPhysics_Player::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const {
info->invMass = invMass;
info->invInertiaTensor.Zero();
info->position.Zero();
info->velocity = current.velocity;
}
/*
================
idPhysics_Player::ApplyImpulse
================
*/
void idPhysics_Player::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) {
if ( current.movementType != PM_NOCLIP ) {
current.velocity += impulse * invMass;
}
}
/*
================
idPhysics_Player::IsAtRest
================
*/
bool idPhysics_Player::IsAtRest( void ) const {
return false;
}
/*
================
idPhysics_Player::GetRestStartTime
================
*/
int idPhysics_Player::GetRestStartTime( void ) const {
return -1;
}
/*
================
idPhysics_Player::SaveState
================
*/
void idPhysics_Player::SaveState( void ) {
saved = current;
}
/*
================
idPhysics_Player::RestoreState
================
*/
void idPhysics_Player::RestoreState( void ) {
current = saved;
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
EvaluateContacts();
}
/*
================
idPhysics_Player::SetOrigin
================
*/
void idPhysics_Player::SetOrigin( const idVec3 &newOrigin, int id ) {
idVec3 masterOrigin;
idMat3 masterAxis;
current.localOrigin = newOrigin;
if ( masterEntity ) {
self->GetMasterPosition( masterOrigin, masterAxis );
current.origin = masterOrigin + newOrigin * masterAxis;
}
else {
current.origin = newOrigin;
}
clipModel->Link( gameLocal.clip, self, 0, newOrigin, clipModel->GetAxis() );
}
/*
================
idPhysics_Player::GetOrigin
================
*/
const idVec3 & idPhysics_Player::PlayerGetOrigin( void ) const {
return current.origin;
}
/*
================
idPhysics_Player::SetAxis
================
*/
void idPhysics_Player::SetAxis( const idMat3 &newAxis, int id ) {
clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), newAxis );
}
/*
================
idPhysics_Player::Translate
================
*/
void idPhysics_Player::Translate( const idVec3 &translation, int id ) {
current.localOrigin += translation;
current.origin += translation;
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
}
/*
================
idPhysics_Player::Rotate
================
*/
void idPhysics_Player::Rotate( const idRotation &rotation, int id ) {
idVec3 masterOrigin;
idMat3 masterAxis;
current.origin *= rotation;
if ( masterEntity ) {
self->GetMasterPosition( masterOrigin, masterAxis );
current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose();
}
else {
current.localOrigin = current.origin;
}
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() );
}
/*
================
idPhysics_Player::SetLinearVelocity
================
*/
void idPhysics_Player::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) {
current.velocity = newLinearVelocity;
}
/*
================
idPhysics_Player::GetLinearVelocity
================
*/
const idVec3 &idPhysics_Player::GetLinearVelocity( int id ) const {
return current.velocity;
}
bool idPhysics_Player::HasRunningVelocity()
{
// Return true when about 7% above walkspeed
return (current.velocity.LengthSqr() > Square(pm_walkspeed.GetFloat()) * 1.15);
}
/*
================
idPhysics_Player::SetPushed
================
*/
void idPhysics_Player::SetPushed( int deltaTime ) {
if ( deltaTime <= 0 ) {
return;
}
// Guard against division-by-near-zero on high refresh rates (>250 Hz)
float dtSec = (float)deltaTime * idMath::M_MS2SEC;
if ( dtSec < 0.001f ) {
dtSec = 0.001f;
}
// Calculate velocity with which the player is pushed by the mover displacement
idVec3 velocity = ( current.origin - saved.origin ) / dtSec;
// Remove any downward push velocity along gravity
float d = velocity * gravityNormal;
if ( d > 0.0f ) {
velocity -= d * gravityNormal;
}
// Accumulate push velocity so the full rotational and translational
// momentum of the catapult is transferred to the player
current.pushVelocity += velocity;
}
/*
================
idPhysics_Player::GetPushedLinearVelocity
================
*/
const idVec3 &idPhysics_Player::GetPushedLinearVelocity( const int id ) const {
return current.pushVelocity;
}
/*
================
idPhysics_Player::ClearPushedVelocity
================
*/
void idPhysics_Player::ClearPushedVelocity( void ) {
current.pushVelocity.Zero();
}
/*
================
idPhysics_Player::SetMaster
the binding is never orientated
================
*/
void idPhysics_Player::SetMaster( idEntity *master, const bool orientated ) {
idVec3 masterOrigin;
idMat3 masterAxis;
if ( master ) {
if ( !masterEntity ) {
// transform from world space to master space
self->GetMasterPosition( masterOrigin, masterAxis );
current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose();
masterEntity = master;
masterYaw = masterAxis[0].ToYaw();
}
ClearContacts();
}
else {
if ( masterEntity ) {
masterEntity = NULL;
}
}
}
const float PLAYER_VELOCITY_MAX = 4000;
const int PLAYER_VELOCITY_TOTAL_BITS = 16;
const int PLAYER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PLAYER_VELOCITY_MAX ) ) + 1;
const int PLAYER_VELOCITY_MANTISSA_BITS = PLAYER_VELOCITY_TOTAL_BITS - 1 - PLAYER_VELOCITY_EXPONENT_BITS;
const int PLAYER_MOVEMENT_TYPE_BITS = 3;
const int PLAYER_MOVEMENT_FLAGS_BITS = 8;
/*
================
idPhysics_Player::WriteToSnapshot
================
*/
void idPhysics_Player::WriteToSnapshot( idBitMsgDelta &msg ) const {
msg.WriteFloat( current.origin[0] );
msg.WriteFloat( current.origin[1] );
msg.WriteFloat( current.origin[2] );
msg.WriteFloat( current.velocity[0], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteFloat( current.velocity[1], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteFloat( current.velocity[2], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] );
msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] );
msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] );
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
msg.WriteDeltaFloat( 0.0f, current.stepUp );
msg.WriteBits( current.movementType, PLAYER_MOVEMENT_TYPE_BITS );
msg.WriteBits( current.movementFlags, PLAYER_MOVEMENT_FLAGS_BITS );
msg.WriteDeltaLong( 0, current.movementTime );
}
/*
================
idPhysics_Player::ReadFromSnapshot
================
*/
void idPhysics_Player::ReadFromSnapshot( const idBitMsgDelta &msg ) {
current.origin[0] = msg.ReadFloat();
current.origin[1] = msg.ReadFloat();
current.origin[2] = msg.ReadFloat();
current.velocity[0] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.velocity[1] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.velocity[2] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] );
current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] );
current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] );
current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
current.stepUp = msg.ReadDeltaFloat( 0.0f );
current.movementType = msg.ReadBits( PLAYER_MOVEMENT_TYPE_BITS );
current.movementFlags = msg.ReadBits( PLAYER_MOVEMENT_FLAGS_BITS );
current.movementTime = msg.ReadDeltaLong( 0 );
if ( clipModel ) {
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
}
}
//################################################################
// Start Mantling Mod
//################################################################
float idPhysics_Player::GetMantleTimeForPhase(EMantlePhase mantlePhase)
{
// Current implementation uses constants
switch (mantlePhase)
{
case hang_DarkModMantlePhase:
return cv_pm_mantle_hang_msecs.GetFloat();
case pull_DarkModMantlePhase:
return cv_pm_mantle_pull_msecs.GetFloat();
case pullFast_DarkModMantlePhase:
return cv_pm_mantle_pullFast_msecs.GetFloat();
case shiftHands_DarkModMantlePhase:
return cv_pm_mantle_shift_hands_msecs.GetFloat();
case push_DarkModMantlePhase:
return cv_pm_mantle_push_msecs.GetFloat();
case pushNonCrouched_DarkModMantlePhase:
return cv_pm_mantle_pushNonCrouched_msecs.GetFloat();
case canceling_DarkModMantlePhase:
assert(m_fmantleCancelDist >= 0.0f);
return m_fmantleCancelDist * 1000.0 / cv_pm_mantle_cancel_speed.GetFloat();
default:
return 0.0f;
}
}
//----------------------------------------------------------------------
void idPhysics_Player::MantleMove()
{
idVec3 newPosition = current.origin;
idVec3 totalMove(0,0,0);
float timeForMantlePhase = GetMantleTimeForPhase(m_mantlePhase);
// Compute proportion into the current movement phase which we are
float timeRatio = 1.0f;
if (timeForMantlePhase != 0)
{
timeRatio = (timeForMantlePhase - m_mantleTime) / timeForMantlePhase;
}
// Branch based on phase
if (m_mantlePhase == hang_DarkModMantlePhase)
{
// Starting at current position, hanging, rocking a bit.
float rockDistance = 0.5f * cv_pm_mantle_roll_mod.GetFloat();
newPosition = m_mantlePullStartPos;
float timeRadians = idMath::PI * timeRatio;
viewAngles.roll = idMath::Sin(timeRadians) * rockDistance;
newPosition += (idMath::Sin(timeRadians) * rockDistance) * viewRight;
if (self != NULL)
{
static_cast<idPlayer*>(self)->SetViewAngles(viewAngles);
}
}
else if (m_mantlePhase == pull_DarkModMantlePhase || m_mantlePhase == pullFast_DarkModMantlePhase)
{
// Player pulls themself up to shoulder even with the surface
totalMove = m_mantlePullEndPos - m_mantlePullStartPos;
float factor = 0.5f * ( 1.0f + idMath::Sin( (timeRatio * 2.0f - 1.0f) * idMath::PI/2 ) );
newPosition = m_mantlePullStartPos + (totalMove * factor);
}
else if (m_mantlePhase == shiftHands_DarkModMantlePhase)
{
// Rock back and forth a bit?
float rockDistance = 1.0f;
// Adjust rockDistance based on duration to reduce abrupt view change
if (cv_pm_mantle_shift_hands_msecs.GetFloat() < 500.0f)
rockDistance = cv_pm_mantle_shift_hands_msecs.GetFloat() / 500.0f;
// Apply mantle roll mod
rockDistance *= cv_pm_mantle_roll_mod.GetFloat();
newPosition = m_mantlePullEndPos;
float timeRadians = idMath::PI * timeRatio;
newPosition += (idMath::Sin(timeRadians) * rockDistance) * viewRight;
viewAngles.roll = idMath::Sin(timeRadians) * rockDistance;
if (self != NULL)
{
static_cast<idPlayer*>(self)->SetViewAngles(viewAngles);
}
}
else if (m_mantlePhase == push_DarkModMantlePhase || m_mantlePhase == pushNonCrouched_DarkModMantlePhase)
{
// Rocking back and forth to get legs up over edge
// STiFU #4930: Reduce rockdistance for pushNonCrouched
const float rockDistance =
((m_mantlePhase == push_DarkModMantlePhase) ? 10.0f : 5.0f)
* cv_pm_mantle_roll_mod.GetFloat();
// Player pushes themselves upward to get their legs onto the surface
totalMove = m_mantlePushEndPos - m_mantlePullEndPos;
newPosition = m_mantlePullEndPos + (totalMove * idMath::Sin(timeRatio * (idMath::PI/2)) );
// We go into duck during this phase and stay there until end
if (m_mantlePhase == push_DarkModMantlePhase)
current.movementFlags |= PMF_DUCKED;
float timeRadians = idMath::PI * timeRatio;
newPosition += (idMath::Sin (timeRadians) * rockDistance) * viewRight;
viewAngles.roll = idMath::Sin (timeRadians) * rockDistance;
// stgatilov: set precise values at the very end of animation
if (timeRatio == 1.0f)
{
assert((newPosition - m_mantlePushEndPos).Length() <= 0.1f);
newPosition = m_mantlePushEndPos;
assert(fabs(viewAngles.roll) <= 0.1f);
viewAngles.roll = 0.0f;
}
if (self != NULL)
{
static_cast<idPlayer*>(self)->SetViewAngles(viewAngles);
}
}
else if (m_mantlePhase == canceling_DarkModMantlePhase)
{
// STiFU #4509: Use linear animation instead of sinus here so that we can
// maintain speed when the animation is finished resulting in a smoother
// transition to AirMove()
totalMove = m_mantleCancelEndPos - m_mantleCancelStartPos;
newPosition = m_mantleCancelStartPos + timeRatio*totalMove;
float timeRadians = idMath::HALF_PI * timeRatio;
viewAngles.roll = idMath::Cos(timeRadians) * m_mantleCancelStartRoll * cv_pm_mantle_roll_mod.GetFloat();
if (self != NULL)
{
static_cast<idPlayer*>(self)->SetViewAngles(viewAngles);
}
}
// If there is a mantled entity, positions are relative to it.
// Transform position to be relative to world origin.
// (For now, translation only, TODO: Add rotation)
if (m_p_mantledEntity != NULL)
{
idPhysics* p_physics = m_p_mantledEntity->GetPhysics();
if (p_physics != NULL)
{
// Ishtvan: Track rotation as well
// newPosition += p_physics->GetOrigin();
newPosition = p_physics->GetOrigin() + p_physics->GetAxis() * newPosition;
if (IsMantleEndPosClipping(p_physics))
CancelMantle();
}
}
SetOrigin(newPosition);
}
const bool idPhysics_Player::IsMantleEndPosClipping(idPhysics* pPhysicsMantledEntity)
{
if (pPhysicsMantledEntity == nullptr)
return false;
// Transform coordinates relative to entity to world
const idVec3 mantleEndWorld = pPhysicsMantledEntity->GetOrigin()
+ pPhysicsMantledEntity->GetAxis() * m_mantlePushEndPos;
// Load appropriate clipping model
if (current.movementFlags & PMF_DUCKED)
{
// Load crouching model
idBounds bounds = clipModel->GetBounds();
bounds[1][2] = pm_crouchheight.GetFloat();
clipModel->LoadModel(pm_usecylinder.GetBool() ? idTraceModel(bounds, 8) : idTraceModel(bounds));
}
else
{
// Load standing model
idBounds bounds = clipModel->GetBounds();
bounds[1][2] = pm_normalheight.GetFloat();
clipModel->LoadModel(pm_usecylinder.GetBool() ? idTraceModel(bounds, 8) : idTraceModel(bounds));
}
// Check clipping
trace_t endPosTrace;
gameLocal.clip.Translation(endPosTrace, mantleEndWorld, mantleEndWorld,
clipModel, clipModel->GetAxis(), clipMask, self);
if (endPosTrace.fraction >= 1.0f)
{
// No clipping
return false;
}
else
{
// We intersect with world geometry
if ((current.movementFlags & PMF_DUCKED) == 0)
{
// We will clip standing up. Go to ducked state and retry
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("MantleMod: Clipping into world. Going to crouched state\r");
current.movementFlags |= PMF_DUCKED;
return IsMantleEndPosClipping(pPhysicsMantledEntity);
}
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("MantleMod: Clipping into world. Canceling mantle.\r");
return true;
}
}
//----------------------------------------------------------------------
void idPhysics_Player::UpdateMantleTimers()
{
// Frame seconds left
float framemSecLeft = framemsec;
// Update jump held down timer: This actually grows, not drops
if (!( current.movementFlags & PMF_JUMP_HELD ) )
{
m_jumpHeldDownTime = 0;
}
else
{
m_jumpHeldDownTime += framemsec;
}
// Skip all this if done mantling
if (m_mantlePhase != notMantling_DarkModMantlePhase && m_mantlePhase != fixClipping_DarkModMantlePhase)
{
idPlayer* player = static_cast<idPlayer*>(self); // grayman #3010
// Handle expiring mantle phases
while (framemSecLeft >= m_mantleTime && m_mantlePhase != notMantling_DarkModMantlePhase && m_mantlePhase != fixClipping_DarkModMantlePhase)
{
framemSecLeft -= m_mantleTime;
m_mantleTime = 0;
// Advance mantle phase
switch (m_mantlePhase)
{
case hang_DarkModMantlePhase:
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING ("MantleMod: Pulling up...\r");
m_mantlePhase = pull_DarkModMantlePhase;
player->StartSound("snd_player_mantle_rustle_short", SND_CHANNEL_BODY3, 0, false, NULL);
player->StartSound("snd_player_mantle_pull", SND_CHANNEL_VOICE, 0, false, NULL); // grayman #3010
break;
case pull_DarkModMantlePhase:
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING ("MantleMod: Shifting hand position...\r");
m_mantlePhase = shiftHands_DarkModMantlePhase;
break;
case pullFast_DarkModMantlePhase: // STiFU #4945: Skip shift hands
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("MantleMod: Quickly pushing self up...\r");
m_mantlePhase = pushNonCrouched_DarkModMantlePhase;
// Go into crouch
current.movementFlags |= PMF_DUCKED;
player->StartSound("snd_player_mantle_rustle_short", SND_CHANNEL_BODY3, 0, false, NULL);
player->StartSound("snd_player_mantle_push", SND_CHANNEL_VOICE, 0, false, NULL); // grayman #3010
break;
case shiftHands_DarkModMantlePhase:
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING ("MantleMod: Pushing self up...\r");
m_mantlePhase = push_DarkModMantlePhase;
// Go into crouch
current.movementFlags |= PMF_DUCKED;
player->StartSound("snd_player_mantle_rustle_short", SND_CHANNEL_BODY3, 0, false, NULL);
player->StartSound("snd_player_mantle_push", SND_CHANNEL_VOICE, 0, false, NULL); // grayman #3010
break;
case pushNonCrouched_DarkModMantlePhase:
case push_DarkModMantlePhase:
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING ("MantleMod: mantle completed\r");
//stgatilov: finish mantling animation completely (#4435)
//(set position of player exactly to the mantle endpoint)
MantleMove();
// check for clipping problems after mantling
// will advance to notMantling when the player isn't clipping
m_mantlePhase = fixClipping_DarkModMantlePhase;
break;
case canceling_DarkModMantlePhase:
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("MantleMod: cancel mantle completed\r");
MantleMove();
static_cast<idPlayer*>(self)->SetImmobilization("MantleMove", 0);
m_fmantleCancelDist = -1.0f;
m_mantlePhase = notMantling_DarkModMantlePhase;
m_mantleTime = 0.0f;
break;
default:
m_mantlePhase = notMantling_DarkModMantlePhase;
break;
}
// Get time it takes to perform a mantling phase
m_mantleTime = GetMantleTimeForPhase(m_mantlePhase);
// Handle end of mantle
if (m_mantlePhase == fixClipping_DarkModMantlePhase)
{
// Handle end of mantle
// Ishtvan 11/20/05 - Raise weapons after mantle is done
static_cast<idPlayer*>(self)->SetImmobilization("MantleMove", 0);
// The mantle consumes all velocity
current.velocity.Zero();
}
}
// Reduce mantle timer
if (m_mantlePhase == fixClipping_DarkModMantlePhase)
{
m_mantleTime = 0;
}
else
{
m_mantleTime -= framemSecLeft;
}
} // This code block is executed only if phase != notMantling && phase != fixClipping
}
//----------------------------------------------------------------------
bool idPhysics_Player::IsMantling() const
{
return m_mantlePhase != notMantling_DarkModMantlePhase && m_mantlePhase != fixClipping_DarkModMantlePhase;
}
//----------------------------------------------------------------------
EMantlePhase idPhysics_Player::GetMantlePhase() const
{
return m_mantlePhase;
}
//----------------------------------------------------------------------
void idPhysics_Player::CancelMantle()
{
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING ("Mantle cancelled\r");
// STiFU #4509: Add canceling animation
// - Move back to last non-clipping location
// - Rotate back viewAngles.roll to 0
m_p_mantledEntity = NULL;
m_mantlePhase = canceling_DarkModMantlePhase;
m_mantleCancelStartPos = GetOrigin();
m_mantleCancelStartRoll = viewAngles.roll;
// Find last non clipping location
// NOTE: There is no guarantee the end position does not clip by the time the
// canceling animation is finished. But there is nothing we can do about that.
trace_t cancelEndPosTrace;
gameLocal.clip.Translation(cancelEndPosTrace, m_mantleStartPosWorld, m_mantleCancelStartPos,
clipModel, clipModel->GetAxis(), clipMask, self);
m_mantleCancelEndPos = m_mantleStartPosWorld + cancelEndPosTrace.fraction * (m_mantleCancelStartPos - m_mantleStartPosWorld);
// Set current velocity: Null the XY-component so the player falls down after the cancel animation
idVec3 mantleCancelDir = m_mantleCancelEndPos - m_mantleCancelStartPos;
m_fmantleCancelDist = fabs(mantleCancelDir.NormalizeFast());
current.velocity = ((mantleCancelDir * cv_pm_mantle_cancel_speed.GetFloat()) * gravityNormal) * gravityNormal;
// Set mantle time. Must be called AFTER setting m_fmantleCancelDist
m_mantleTime = GetMantleTimeForPhase(canceling_DarkModMantlePhase);
// Prevent awkward returning to uncrouched state while falling after
// canceling the mantle
idPlayer* pPlayer = static_cast<idPlayer*>(self);
if (pPlayer == nullptr)
return;
if ((current.movementFlags & PMF_DUCKED) != 0)
{
pPlayer->m_CrouchIntent = true;
pPlayer->m_IdealCrouchState = true;
}
// Play a canceling sound
pPlayer->StartSound("snd_player_mantle_cancel", SND_CHANNEL_ANY, 0, false, NULL);
}
//----------------------------------------------------------------------
//----------------------------------------------------------------------
void idPhysics_Player::StartMantle
(
EMantlePhase initialMantlePhase,
idVec3 eyePos,
idVec3 startPos,
idVec3 endPos
)
{
// Ishtvan 10/16/05
// If mantling starts while on a rope, detach from that rope
if ( m_bOnRope )
{
RopeDetach();
}
// If mantling starts while climbing, detach from climbing surface
if ( m_bOnClimb )
{
ClimbDetach();
}
// Ishtvan 11/20/05 - Lower weapons when mantling
static_cast<idPlayer*>(self)->SetImmobilization( "MantleMove", EIM_ATTACK );
// greebo: Disable the next mantle start here, this is set to TRUE again
// when the jump key is released outside a mantle phase
m_mantleStartPossible = false;
// Calculate mantle distance
idVec3 mantleDistanceVec = endPos - startPos;
idPlayer* player = static_cast<idPlayer*>(self); // grayman #3010
// Init forced-crouch mantle handling
player->ResetForcedCrouchMantle();
// Log starting phase
if (initialMantlePhase == hang_DarkModMantlePhase)
{
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Mantle starting with hang\r");
player->StartSound("snd_player_mantle_impact", SND_CHANNEL_BODY2, 0, false, NULL);
player->StartSound("snd_player_mantle_rustle", SND_CHANNEL_BODY3, 0, false, NULL);
// Impart a force on mantled object?
if (m_p_mantledEntity != NULL && self != NULL)
{
impactInfo_t info;
m_p_mantledEntity->GetImpactInfo(self, m_mantledEntityID, endPos, &info);
if (info.invMass != 0.0f)
{
m_p_mantledEntity->ActivatePhysics(self);
m_p_mantledEntity->ApplyImpulse( self, m_mantledEntityID, endPos, current.velocity / ( info.invMass * 2.0f ) );
}
}
}
else if (initialMantlePhase == pull_DarkModMantlePhase)
{
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Mantle starting with pull upward\r");
player->StartSound("snd_player_mantle_impact", SND_CHANNEL_BODY2, 0, false, NULL);
player->StartSound("snd_player_mantle_rustle", SND_CHANNEL_BODY3, 0, false, NULL);
player->StartSound("snd_player_mantle_pull", SND_CHANNEL_VOICE, 0, false, NULL); // grayman #3010
}
else if (initialMantlePhase == pullFast_DarkModMantlePhase)
{
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Mantle starting with quick silent pull upward\r");
player->StartSound("snd_player_mantle_impact_subtle", SND_CHANNEL_BODY2, 0, false, NULL);
player->StartSound("snd_player_mantle_rustle", SND_CHANNEL_BODY3, 0, false, NULL);
}
else if (initialMantlePhase == shiftHands_DarkModMantlePhase)
{
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Mantle starting with shift hands\r");
}
else if (initialMantlePhase == push_DarkModMantlePhase)
{
// Go into crouch
current.movementFlags |= PMF_DUCKED;
// Start with push upward
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Mantle starting with push upward\r");
player->StartSound("snd_player_mantle_impact_subtle", SND_CHANNEL_BODY2, 0, false, NULL);
player->StartSound("snd_player_mantle_rustle", SND_CHANNEL_BODY3, 0, false, NULL);
player->StartSound("snd_player_mantle_push", SND_CHANNEL_VOICE, 0, false, NULL); // grayman #3010
}
else if (initialMantlePhase == pushNonCrouched_DarkModMantlePhase)
{
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Mantle starting with push non-crouched upward\r");
// We make contact with the feet. Play footstep sound and the endpos
idPlayer* pPlayer = dynamic_cast<idPlayer*>(self);
if (pPlayer)
pPlayer->PlayPlayerFootStepSound(&endPos, true);
// Play grunt at high velocity
if (current.velocity.LengthSqr() >
pow(cv_pm_mantle_pushNonCrouched_playgrunt_speedthreshold.GetFloat(), 2))
{
player->StartSound("snd_player_mantle_push", SND_CHANNEL_VOICE, 0, false, NULL);
}
}
// #6231: Delayed fall damage on mantle
// Zero out all velocity sooner than at the end of the mantle so
// that the player receives damage feedback immediately on impact
// from a fall rather than at the end of the mantle animation.
// Also, zeroing out all velocity at mantle start makes sense,
// because the animation is stop, mantle, stop.
current.velocity.Zero();
m_mantlePhase = initialMantlePhase;
m_mantleTime = GetMantleTimeForPhase(m_mantlePhase);
// Make positions relative to entity
if (m_p_mantledEntity != NULL)
{
idPhysics* p_physics = m_p_mantledEntity->GetPhysics();
if (p_physics != NULL)
{
const idVec3& mantledEntityOrigin = p_physics->GetOrigin();
const idMat3& mantledEntityAxis = p_physics->GetAxis();
// ishtvan 1/3/2010: Incorporate entity rotation as well as translation
startPos = (startPos - mantledEntityOrigin) * mantledEntityAxis.Transpose();
eyePos = (eyePos - mantledEntityOrigin) * mantledEntityAxis.Transpose();
endPos = (endPos - mantledEntityOrigin) * mantledEntityAxis.Transpose();
}
}
// Set end position
m_mantlePushEndPos = endPos;
if ( initialMantlePhase == pull_DarkModMantlePhase
|| initialMantlePhase == hang_DarkModMantlePhase )
{
// Pull from start position up to about 2/3 of eye height
m_mantlePullStartPos = startPos;
m_mantlePullEndPos = eyePos;
m_mantlePullEndPos += GetGravityNormal() * pm_normalheight.GetFloat() / 4.5f;
}
else if (initialMantlePhase == pullFast_DarkModMantlePhase)
{
// Pull from start position up to eye height
m_mantlePullStartPos = startPos;
m_mantlePullEndPos = eyePos;
}
else
{
// Starting with push from current position
m_mantlePullEndPos = startPos;
}
m_mantleStartPosWorld = GetOrigin();
}
//----------------------------------------------------------------------
bool idPhysics_Player::CheckJumpHeldDown()
{
return m_jumpHeldDownTime > cv_pm_mantle_jump_hold_trigger.GetInteger();
}
//----------------------------------------------------------------------
void idPhysics_Player::GetCurrentMantlingReachDistances
(
float& out_maxVerticalReachDistance,
float& out_maxHorizontalReachDistance,
float& out_maxMantleTraceDistance
)
{
// Determine arm reach in each direction
float armReach = pm_normalheight.GetFloat() * cv_pm_mantle_reach.GetFloat();
float armVerticalReach = pm_normalheight.GetFloat() * cv_pm_mantle_height.GetFloat();
// Trace out as far as horizontal arm length from player
out_maxMantleTraceDistance = armReach;
// Determine maximum vertical and horizontal distance components for
// a mantleable surface
if (current.movementFlags & PMF_DUCKED /*&& !OnRope() && !OnLadder()*/)
{
out_maxVerticalReachDistance = pm_crouchheight.GetFloat() + armVerticalReach;
out_maxHorizontalReachDistance = armReach;
}
/*else if (OnRope() || OnLadder())
{
// angua: need larger reach when on rope
out_maxVerticalReachDistance = pm_normalheight.GetFloat() + armVerticalReach;
out_maxHorizontalReachDistance = 2* armReach;
out_maxMantleTraceDistance *= 2;
}*/
else
{
// This vertical distance is up from the players feet
out_maxVerticalReachDistance = pm_normalheight.GetFloat() + armVerticalReach;
out_maxHorizontalReachDistance = armReach;
}
}
//----------------------------------------------------------------------
void idPhysics_Player::MantleTargetTrace
(
float maxMantleTraceDistance,
const idVec3& eyePos,
const idVec3& forwardVec,
trace_t& out_trace
)
{
// Calculate end point of gaze trace
idVec3 end = eyePos + (maxMantleTraceDistance * forwardVec);
// Run gaze trace
gameLocal.clip.TracePoint( out_trace, eyePos, end, MASK_SOLID, self );
// If that trace didn't hit anything, try a taller trace forward along the midline
// of the player's body for the full player's height out the trace distance.
if ( out_trace.fraction >= 1.0f )
{
idVec3 upVector = -GetGravityNormal();
// Project forward vector onto the a plane perpendicular to gravity
idVec3 forwardPerpGrav = forwardVec;
forwardPerpGrav.ProjectOntoPlane(upVector);
// Create bounds for translation trace model
idBounds bounds = clipModel->GetBounds();
idBounds savedBounds = bounds;
bounds[0][1] = (savedBounds[0][1] + savedBounds[1][1]) / 2;
bounds[0][1] -= 0.01f;
bounds[1][1] = bounds[0][1] + 0.02f;
bounds[0][0] = bounds[0][1];
bounds[1][0] = bounds[1][1];
clipModel->LoadModel( pm_usecylinder.GetBool() ? idTraceModel(bounds, 8) : idTraceModel(bounds) );
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Mantle gaze trace didn't hit anything, so doing forward movement trace for mantle target\r");
gameLocal.clip.Translation
(
out_trace,
current.origin,
current.origin + (maxMantleTraceDistance * forwardPerpGrav),
clipModel,
clipModel->GetAxis(),
MASK_SOLID,
self
);
//gameRenderWorld->DebugBounds(colorCyan, bounds, current.origin, 2000);
//gameRenderWorld->DebugBounds(colorBlue, bounds, current.origin + (maxMantleTraceDistance * forwardPerpGrav), 2000);
// Restore player clip model to normal
clipModel->LoadModel( pm_usecylinder.GetBool() ? idTraceModel(savedBounds, 8) : idTraceModel(savedBounds) );
}
// Get the entity to be mantled
if (out_trace.c.entityNum != ENTITYNUM_NONE)
{
// Track entity which is was the chosen target
m_p_mantledEntity = gameLocal.entities[out_trace.c.entityNum];
if (m_p_mantledEntity->IsMantleable())
{
m_mantledEntityID = out_trace.c.id;
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Mantle target entity is called '%s'\r", m_p_mantledEntity->name.c_str());
}
else
{
// Oops, this entity isn't mantleable
m_p_mantledEntity = NULL;
out_trace.fraction = 1.0f; // Pretend we didn't hit anything
}
}
}
//----------------------------------------------------------------------
idPhysics_Player::EMantleable idPhysics_Player::DetermineIfMantleTargetHasMantleableSurface
(
float maxVerticalReachDistance,
float maxHorizontalReachDistance,
trace_t& in_targetTraceResult,
idVec3& out_mantleEndPoint
)
{
// Never mantle onto non-mantleable entities (early exit)
if (in_targetTraceResult.fraction < 1.0f)
{
idEntity* ent = gameLocal.entities[in_targetTraceResult.c.entityNum];
if (ent == NULL || !ent->IsMantleable())
{
// The mantle target is an unmantleable entity
return EMantleable_No;
}
}
// Try moving player's bounding box up from the trace hit point
// in steps up to the maximum distance and see if at any point
// there are no collisions. If so, we can mantle.
// First point to test has gravity orthogonal coordinates set
// to the ray trace collision point. It then has gravity non-orthogonal
// coordinates set from the current player origin. However,
// for the non-orthogonal-to-gravity coordinates, the trace.c.point
// location is a better starting place. Because of rear surface occlusion,
// it will always be closer to the actual "upper" surface than the player
// origin unless the object is "below" the player relative to gravity.
// And, in that "below" case, mantling isn't possible anyway.
// This sets coordinates to their components which are orthogonal
// to gravity.
idVec3 componentOrthogonalToGravity = in_targetTraceResult.c.point;
componentOrthogonalToGravity.ProjectOntoPlane(-gravityNormal);
// This sets coordintes to their components parallel to gravity
idVec3 componentParallelToGravity;
componentParallelToGravity.x = -gravityNormal.x * in_targetTraceResult.c.point.x;
componentParallelToGravity.y = -gravityNormal.y * in_targetTraceResult.c.point.y;
componentParallelToGravity.z = -gravityNormal.z * in_targetTraceResult.c.point.z;
// What parallel to gravity reach distance is already used up at this point
idVec3 originParallelToGravity;
originParallelToGravity.x = -gravityNormal.x * current.origin.x;
originParallelToGravity.y = -gravityNormal.y * current.origin.y;
originParallelToGravity.z = -gravityNormal.z * current.origin.z;
float verticalReachDistanceUsed = (componentParallelToGravity - originParallelToGravity).Length();
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING
(
"Initial vertical reach distance used = %f out of maximum of %f\r",
verticalReachDistanceUsed,
maxVerticalReachDistance
);
// The first test point
idVec3 testPosition = componentOrthogonalToGravity + componentParallelToGravity;
// Load crouch model
// as mantling ends in a crouch
if (!(current.movementFlags & PMF_DUCKED))
{
idBounds bounds = clipModel->GetBounds();
bounds[1][2] = pm_crouchheight.GetFloat();
clipModel->LoadModel( pm_usecylinder.GetBool() ? idTraceModel(bounds, 8) : idTraceModel(bounds) );
}
// We try moving it up by the step distance up to the maximum height until
// there are no collisions
bool b_keepTesting = verticalReachDistanceUsed < maxVerticalReachDistance;
bool b_mantlePossible = false;
bool b_lastCollisionWasMantleable = true;
trace_t worldMantleTrace;
while (b_keepTesting)
{
// Try collision in_targetTraceResult
idVec3 mantleTraceStart = testPosition;
gameLocal.clip.Translation( worldMantleTrace, mantleTraceStart, testPosition, clipModel, clipModel->GetAxis(), clipMask, self );
if (worldMantleTrace.fraction >= 1.0f)
{
// We can mantle to there, unless the last test collided with something non-mantleable.
// Either way we're done here.
b_keepTesting = false;
if (b_lastCollisionWasMantleable)
{
b_mantlePossible = true;
}
}
else
{
idEntity* ent = gameLocal.entities[ worldMantleTrace.c.entityNum ];
if (ent && !ent->IsMantleable())
{
// If we collided with a non-mantleable entity, then flag that.
// This is to prevent situations where we start out mantling on a low ledge
// (like a stair) on which a non-mantleable entity (like an AI) is standing,
// and proceed to mantle over the AI.
b_lastCollisionWasMantleable = false;
}
else
{
// On the other hand, if there's a shelf above the AI, then we can still mantle
// the shelf.
b_lastCollisionWasMantleable = true;
}
if (verticalReachDistanceUsed < maxVerticalReachDistance)
{
// Try next test position
float testIncrementAmount = maxVerticalReachDistance - verticalReachDistanceUsed;
// Establish upper bound for increment test size
if (testIncrementAmount > MANTLE_TEST_INCREMENT)
{
testIncrementAmount = MANTLE_TEST_INCREMENT;
}
// Establish absolute minimum increment size so that
// we don't approach increment size below floating point precision,
// which would cause an infinite loop.
if (testIncrementAmount < 1.0f)
{
testIncrementAmount = 1.0f;
}
// Update location by increment size
componentParallelToGravity += (-gravityNormal * testIncrementAmount);
verticalReachDistanceUsed = (componentParallelToGravity - originParallelToGravity).Length();
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING
(
"Ledge Search: Vertical reach distance used = %f out of maximum of %f\r",
verticalReachDistanceUsed,
maxVerticalReachDistance
);
// Modify test position
testPosition = componentOrthogonalToGravity + componentParallelToGravity;
}
else
{
// No surface we could fit on against gravity from raytrace hit point
// up as far as we can reach
b_keepTesting = false;
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("No mantleable surface within reach distance\r");
}
}
}
// Don't mantle onto surfaces that are too steep.
// Any surface with an angle whose cosine is
// smaller than MIN_FLATNESS is too steep.
float minFlatness = cv_pm_mantle_minflatness.GetFloat();
if (b_mantlePossible)
{
// Attempt to get the normal of the surface we'd be standing on
// In rare cases this may not collide
trace_t floorTrace;
gameLocal.clip.Translation( floorTrace, testPosition,
testPosition + (gravityNormal * MANTLE_TEST_INCREMENT),
clipModel, clipModel->GetAxis(), clipMask, self );
if (floorTrace.fraction < 1.0f)
{
// Uses the dot product to compare against the cosine of an angle.
// Comparing to cos(90)=0 means we can mantle on top of any surface
// Comparing to cos(0)=1 means we can only mantle on top of perfectly flat surfaces
float flatness = floorTrace.c.normal * (-gravityNormal);
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING(
"Floor %.2f,%.2f,%.2f; grav %.2f,%.2f,%.2f; dot %f; %s\r",
floorTrace.c.normal.x, floorTrace.c.normal.y, floorTrace.c.normal.z,
gravityNormal.x, gravityNormal.y, gravityNormal.z,
flatness, flatness < minFlatness ? "too steep" : "OK");
if (flatness < minFlatness)
{
b_mantlePossible = false;
}
}
}
// Return Val
EMantleable Mantleable = EMantleable_No;
if (b_mantlePossible)
{
out_mantleEndPoint = testPosition;
Mantleable = EMantleable_YesCrouched;
}
// Must restore standing model if player is not crouched
if (!(current.movementFlags & PMF_DUCKED))
{
// Load back standing model
idBounds bounds = clipModel->GetBounds();
bounds[1][2] = pm_normalheight.GetFloat();
clipModel->LoadModel( pm_usecylinder.GetBool() ? idTraceModel(bounds, 8) : idTraceModel(bounds) );
// STiFU #4930: Test if the end position can also be reached standing up.
// Do this only if player is non-crouched
if (b_mantlePossible)
{
trace_t ceilTrace;
gameLocal.clip.Translation(ceilTrace, testPosition, testPosition,
clipModel, clipModel->GetAxis(), clipMask, self);
if (ceilTrace.fraction >= 1.0f)
{
Mantleable = EMantleable_YesUpstraight;
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Surface can be mantled upstraight\r");
}
}
}
return Mantleable;
}
//----------------------------------------------------------------------
bool idPhysics_Player::DetermineIfPathToMantleSurfaceIsPossible
(
float maxVerticalReachDistance,
float maxHorizontalReachDistance,
bool testCrouched,
const idVec3& in_eyePos,
const idVec3& in_mantleStartPoint,
const idVec3& in_mantleEndPoint
)
{
// Make sure path from current location
// upward can be traversed.
trace_t roomForMoveUpTrace;
idVec3 MoveUpStart = in_mantleStartPoint;
idVec3 MoveUpEnd;
// Go to coordinate components against gravity from current location
idVec3 componentOrthogonalToGravity;
componentOrthogonalToGravity = in_mantleStartPoint;
componentOrthogonalToGravity.ProjectOntoPlane (-gravityNormal);
MoveUpEnd = componentOrthogonalToGravity;
MoveUpEnd.x += -gravityNormal.x * in_mantleEndPoint.x;
MoveUpEnd.y += -gravityNormal.y * in_mantleEndPoint.y;
MoveUpEnd.z += -gravityNormal.z * in_mantleEndPoint.z;
// Change clip model if needed
bool clipModelChanged = false;
if (testCrouched && !(current.movementFlags & PMF_DUCKED))
{
// Load crouching model
idBounds bounds = clipModel->GetBounds();
bounds[1][2] = pm_crouchheight.GetFloat();
clipModel->LoadModel( pm_usecylinder.GetBool() ? idTraceModel(bounds, 8) : idTraceModel(bounds) );
clipModelChanged = true;
}
else if (!testCrouched && (current.movementFlags & PMF_DUCKED))
{
// Load standing model
idBounds bounds = clipModel->GetBounds();
bounds[1][2] = pm_normalheight.GetFloat();
clipModel->LoadModel(pm_usecylinder.GetBool() ? idTraceModel(bounds, 8) : idTraceModel(bounds));
clipModelChanged = true;
}
gameLocal.clip.Translation
(
roomForMoveUpTrace,
MoveUpStart,
MoveUpEnd,
clipModel,
clipModel->GetAxis(),
clipMask,
self
);
// Change back clip model
if (clipModelChanged)
{
if (testCrouched)
{
// Load back standing model
idBounds bounds = clipModel->GetBounds();
bounds[1][2] = pm_normalheight.GetFloat();
clipModel->LoadModel(pm_usecylinder.GetBool() ? idTraceModel(bounds, 8) : idTraceModel(bounds));
}
else
{
// Load back crouching model
idBounds bounds = clipModel->GetBounds();
bounds[1][2] = pm_crouchheight.GetFloat();
clipModel->LoadModel(pm_usecylinder.GetBool() ? idTraceModel(bounds, 8) : idTraceModel(bounds));
}
}
// Log
if (roomForMoveUpTrace.fraction < 1.0)
{
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING
(
"Collision test from (%f %f %f) to (%f %f %f) yieled trace fraction %f\r",
MoveUpStart.x,
MoveUpStart.y,
MoveUpStart.z,
MoveUpEnd.x,
MoveUpEnd.y,
MoveUpEnd.z,
roomForMoveUpTrace.fraction
);
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING ("Not enough vertical clearance along mantle path\r");
return false;
}
else
{
return true;
}
}
//----------------------------------------------------------------------
idPhysics_Player::EMantleable idPhysics_Player::ComputeMantlePathForTarget
(
float maxVerticalReachDistance,
float maxHorizontalReachDistance,
const idVec3& eyePos,
trace_t& in_targetTraceResult,
idVec3& out_mantleEndPoint
)
{
// Up vector
idVec3 upVector = -GetGravityNormal();
// Mantle start point is origin
const idVec3& mantleStartPoint = GetOrigin();
// Check if trace target has a mantleable surface
EMantleable IsSurfaceMantleable = DetermineIfMantleTargetHasMantleableSurface
(
maxVerticalReachDistance,
maxHorizontalReachDistance,
in_targetTraceResult,
out_mantleEndPoint
);
if (IsSurfaceMantleable > EMantleable_No)
{
// Check if path to mantle end point is not blocked
if (IsSurfaceMantleable == EMantleable_YesUpstraight)
{
// STiFU #4930: Try standing up first, if that fails, try crouched
static const bool bCrouchedTest = false;
const bool bPathClearStandingUp = DetermineIfPathToMantleSurfaceIsPossible(
maxVerticalReachDistance,
maxHorizontalReachDistance,
bCrouchedTest,
eyePos,
mantleStartPoint,
out_mantleEndPoint
);
if (!bPathClearStandingUp)
IsSurfaceMantleable = EMantleable_YesCrouched;
}
if (IsSurfaceMantleable != EMantleable_YesUpstraight)
{
static const bool bCrouchedTest = true;
const bool bPathClearCrouched = DetermineIfPathToMantleSurfaceIsPossible(
maxVerticalReachDistance,
maxHorizontalReachDistance,
bCrouchedTest,
eyePos,
mantleStartPoint,
out_mantleEndPoint
);
if (!bPathClearCrouched)
IsSurfaceMantleable = EMantleable_No;
}
if (IsSurfaceMantleable > EMantleable_No)
{
// Is end point too far away?
idVec3 endDistanceVector = out_mantleEndPoint - eyePos;
float endDistance = endDistanceVector.Length();
idVec3 upDistance = endDistanceVector;
upDistance.x *= upVector.x;
upDistance.y *= upVector.y;
upDistance.z *= upVector.z;
float upDist = upDistance.Length();
float nonUpDist = idMath::Sqrt(endDistance*endDistance - upDist*upDist);
// Check the calculated distances
if (upDist < 0.0)
{
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Mantleable surface was below player's feet. No belly slide allowed.\r");
IsSurfaceMantleable = EMantleable_No;
}
else if (upDist > maxVerticalReachDistance || nonUpDist > maxHorizontalReachDistance)
{
// Its too far away either horizontally or vertically
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING
(
"Distance to end point was (%f, %f) (horizontal, vertical) which is greater than limits of (%f %f), so mantle cannot be done\n",
upDist,
nonUpDist,
maxVerticalReachDistance,
maxHorizontalReachDistance
);
IsSurfaceMantleable = EMantleable_No;
}
// Distances are reasonable
}
}
// Return result
return IsSurfaceMantleable;
}
//----------------------------------------------------------------------
void idPhysics_Player::PerformMantle()
{
// Can't start mantle if already mantling or not yet possible (jump button not yet released)
if ( !(m_mantlePhase == notMantling_DarkModMantlePhase || m_mantlePhase == fixClipping_DarkModMantlePhase)
|| !m_mantleStartPossible )
{
return;
}
idPlayer* p_player = static_cast<idPlayer*>(self);
if (p_player == NULL)
{
DM_LOG(LC_MOVEMENT, LT_ERROR)LOGSTRING("p_player is NULL\r");
return;
}
if (p_player->GetImmobilization() & EIM_MANTLE)
{
return; // greebo: Mantling disabled by immobilization system
}
// Ishtvan: Do not attempt to mantle if holding an object
if (cv_pm_mantle_while_carrying.GetInteger() == 0
&& (p_player->IsShoulderingBody() || gameLocal.m_Grabber->GetSelected()))
{
return;
}
if (waterLevel >= WATERLEVEL_HEAD)
{
return; // STiFU: #1037: Do not mantle underwater
}
// Clear mantled entity members to indicate nothing is
// being mantled
m_p_mantledEntity = NULL;
m_mantledEntityID = 0;
// Forward vector is direction player is looking
idVec3 forward = viewAngles.ToForward();
forward.Normalize();
// We use gravity alot here...
const idVec3& gravityNormal = GetGravityNormal();
// Get maximum reach distances for mantling
float maxVerticalReachDistance;
float maxHorizontalReachDistance;
float maxMantleTraceDistance;
GetCurrentMantlingReachDistances
(
maxVerticalReachDistance,
maxHorizontalReachDistance,
maxMantleTraceDistance
);
// Get start position of gaze trace, which is player's eye position
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING ("Getting eye position\r");
idVec3 eyePos = p_player->GetEyePosition();
// Run mantle trace
trace_t trace;
MantleTargetTrace
(
maxMantleTraceDistance,
eyePos,
forward,
trace
);
// If the trace found a target, see if it is mantleable
if ( trace.fraction < 1.0f )
{
// mantle target found
// Log trace hit point
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING
(
"Mantle target trace collision point (%f %f %f)\r",
trace.c.point.x,
trace.c.point.y,
trace.c.point.z
);
// Find mantle end point and make sure mantle is
// possible
idVec3 mantleEndPoint;
EMantleable IsMantleable = ComputeMantlePathForTarget
(
maxVerticalReachDistance,
maxHorizontalReachDistance,
eyePos,
trace,
mantleEndPoint
);
if (IsMantleable > EMantleable_No)
{
// Mantle target passed mantleability tests
// Log the end point
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING
(
"Mantle end position = (%f %f %f)\r",
mantleEndPoint.x,
mantleEndPoint.y,
mantleEndPoint.z
);
// Start with mantle phase dependent on position relative
// to the mantle end point
const float mantleEndHeight = -(mantleEndPoint * gravityNormal);
float floorHeight = -FLT_MAX;
{
idVec3 floorPos;
if (self->GetFloorPos(pm_normalviewheight.GetFloat(), floorPos))
floorHeight = -floorPos * gravityNormal;
}
const float eyeHeight = -eyePos * gravityNormal;
const float feetHeight = -(GetOrigin() * gravityNormal);
const float obstacleHeight = mantleEndHeight - feetHeight;
const bool bPullOrHang = eyeHeight < mantleEndHeight;
const bool bFallingFast =
(current.velocity * gravityNormal) >
cv_pm_mantle_fallingFast_speedthreshold.GetFloat();
// Daft Mugi #5892: While shouldering a body, allow mantling at approximately waist height.
// Also, allow mantling while holding small objects at approximately waist height.
if (cv_pm_mantle_while_carrying.GetInteger() == 1
&& (p_player->IsShoulderingBody() || gameLocal.m_Grabber->GetSelected())
&& (bPullOrHang // no pull or hang allowed
|| obstacleHeight > cv_pm_mantle_maxShoulderingObstacleHeight.GetFloat() // restrict max height
|| bFallingFast)) // must not be falling fast
{
return;
}
// If holding a small object and in midair, reduce allowed obstacle height.
if (cv_pm_mantle_while_carrying.GetInteger() == 1
&& gameLocal.m_Grabber->GetSelected()
&& !groundPlane
&& obstacleHeight > cv_pm_mantle_maxHoldingMidairObstacleHeight.GetFloat())
{
return;
}
if (cv_pm_mantle_fastLowObstaces.GetBool()) // STiFU #4930
{
if ( IsMantleable == EMantleable_YesUpstraight // Upstraight mantle possible
&& !bFallingFast
&& (mantleEndHeight < floorHeight + cv_pm_mantle_maxLowObstacleHeight.GetFloat() // Only allow the full obstacle height when near the floor
|| mantleEndHeight < feetHeight + cv_pm_mantle_maxLowObstacleHeight.GetFloat()*0.66f)) // Reduce allowed obstacle height midair
{
// Do a fast mantle over low obstacle
StartMantle(pushNonCrouched_DarkModMantlePhase, eyePos, GetOrigin(), mantleEndPoint);
return;
}
}
if (cv_pm_mantle_fastMediumObstaclesCrouched.GetBool()) // STiFU #4945
{
// Use floorHeight instead of feetHeight to allow this mantle also when jump-mantling medium sized obstacles
const bool bIsCrouched = current.movementFlags & PMF_DUCKED;
if ( bIsCrouched
&& !bFallingFast
&& bPullOrHang // When endheight lower than eyes, use the regular push mantle
&& mantleEndHeight < floorHeight + pm_normalviewheight.GetFloat())
{
// Do a fast pull-push mantle over medium sized obstacle
StartMantle(pullFast_DarkModMantlePhase, eyePos, GetOrigin(), mantleEndPoint);
return;
}
}
if (bPullOrHang)
{
// Start with pull if on the ground, hang if not
if (groundPlane)
{
StartMantle(pull_DarkModMantlePhase, eyePos, GetOrigin(), mantleEndPoint);
}
else
{
StartMantle(hang_DarkModMantlePhase, eyePos, GetOrigin(), mantleEndPoint);
}
}
else
{
StartMantle(push_DarkModMantlePhase, eyePos, GetOrigin(), mantleEndPoint);
}
}
}
}
//####################################################################
// End Mantle Mod
// SophisticatedZombie (DH)
//####################################################################
//####################################################################
// Start Leaning Mod
// Zaccheus (some original geometric drawings)
// SophisticatedZombie (DH)
//
//####################################################################
void idPhysics_Player::ToggleLean(float leanYawAngleDegrees)
{
idPlayer* pPlayer = static_cast<idPlayer*>(self);
if (pPlayer == NULL)
{
DM_LOG(LC_MOVEMENT, LT_ERROR)LOGSTRING("pPlayer is NULL\r");
return;
}
if (pPlayer->GetImmobilization() & EIM_LEAN)
// If lean immobilization is set, do nothing!
return;
const bool isLeaning = m_CurrentLeanTiltDegrees >= 0.1f;
const bool isSameDirection = m_leanYawAngleDegrees == leanYawAngleDegrees;
const bool isStopping = m_leanMoveEndTilt == 0;
const bool stopLeanOnKeyUp = !cv_tdm_toggle_lean.GetBool()
&& isLeaning // already leaning
&& isSameDirection // only stop when same direction
&& !isStopping; // not already stopping
const bool stopLeanOnToggle = cv_tdm_toggle_lean.GetBool()
&& isLeaning; // already leaning
if (stopLeanOnKeyUp || stopLeanOnToggle)
{
UnLean(leanYawAngleDegrees);
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("ToggleLean ending lean\r");
return;
}
const bool isForwardLean = leanYawAngleDegrees == 90.0f;
float angle = isForwardLean ? 8.0f : cv_pm_lean_angle.GetFloat();
float standingViewHeight = pm_normalviewheight.GetFloat();
float eyeHeight = pPlayer->EyeHeight();
float eyeHeightDelta = standingViewHeight - eyeHeight;
float crouchAngleAdjustment = (14 + (2 * (eyeHeight / standingViewHeight))) / 16.0f;
float forwardAngleAdjustment = isForwardLean ? (15.0f/16.0f) : 1.0f;
if (!isLeaning || (isStopping && isSameDirection))
{
// Start the lean
m_leanMoveStartTilt = m_CurrentLeanTiltDegrees;
m_leanYawAngleDegrees = leanYawAngleDegrees;
m_leanTime = cv_pm_lean_time_to_lean.GetFloat();
m_leanMoveEndTilt = angle
* forwardAngleAdjustment
* crouchAngleAdjustment;
m_leanMoveMaxAngle = m_leanMoveEndTilt;
m_b_leanFinished = false;
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("ToggleLean starting lean\r");
}
}
void idPhysics_Player::UnLean(float leanYawAngleDegrees)
{
if (m_leanYawAngleDegrees != leanYawAngleDegrees)
{
// Not same direction
return;
}
if (m_leanTime > 0 && m_leanMoveEndTilt == 0)
{
// Already un-leaning
return;
}
// End the lean
m_leanMoveStartTilt = m_CurrentLeanTiltDegrees;
m_leanMoveEndTilt = 0.0;
m_b_leanFinished = false;
m_leanTime = cv_pm_lean_time_to_unlean.GetFloat();
// greebo: Leave the rest of the variables as they are
// to avoid view-jumping issues due to leaning back.
}
//----------------------------------------------------------------------
bool idPhysics_Player::IsLeaning()
{
if (m_CurrentLeanTiltDegrees > 0.0f)
{
// entering, exiting, or holding lean
return true;
}
else
{
return false;
}
}
//----------------------------------------------------------------------
const idAngles& idPhysics_Player::GetViewLeanAngles() const
{
return m_viewLeanAngles;
}
//----------------------------------------------------------------------
idVec3 idPhysics_Player::GetViewLeanTranslation()
{
idAngles viewAngNoPitch = viewAngles;
viewAngNoPitch.pitch = 0.0f;
return viewAngNoPitch.ToMat4() * m_viewLeanTranslation;
}
//----------------------------------------------------------------------
void idPhysics_Player::ProcessPeek(idEntity* peekEntity, idEntity* door, idVec3 normal) // grayman #4882
{
const function_t* func = gameLocal.program.FindFunction("peekThread");
if ( func != NULL )
{
idThread *pThread = new idThread(func);
int n = pThread->GetThreadNum();
pThread->CallFunctionArgs(func, true, "eev", peekEntity, door, &normal);
pThread->DelayedStart(0);
pThread->Execute();
}
}
//----------------------------------------------------------------------
void idPhysics_Player::UpdateLeanAngle (float deltaLeanTiltDegrees)
{
// What would the new lean angle be?
float newLeanTiltDegrees = m_CurrentLeanTiltDegrees + deltaLeanTiltDegrees;
DM_LOG(LC_MOVEMENT,LT_DEBUG)LOGSTRING("newLeanTiltDegrees = %f\r", newLeanTiltDegrees );
if (newLeanTiltDegrees < 0.0)
{
// Adjust delta
deltaLeanTiltDegrees = 0.0 - m_CurrentLeanTiltDegrees;
m_leanTime = 0.0;
m_b_leanFinished = true;
}
else if (newLeanTiltDegrees > m_leanMoveMaxAngle)
{
// Adjust delta
deltaLeanTiltDegrees = m_leanMoveMaxAngle - m_CurrentLeanTiltDegrees;
m_leanTime = 0.0;
m_b_leanFinished = true;
}
// Log max possible lean
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING
(
"Currently leaning %.2f degrees, can lean up to %.2f more degrees this frame\r",
m_CurrentLeanTiltDegrees,
deltaLeanTiltDegrees
);
newLeanTiltDegrees = m_CurrentLeanTiltDegrees + deltaLeanTiltDegrees;
// Collision test: do not change lean angles any more if collision has occurred
// convert proposed angle to a viewpoint in space:
idVec3 newPoint = LeanParmsToPoint( newLeanTiltDegrees );
idPlayer* player = static_cast<idPlayer*>(self);
idVec3 origPoint = player->GetEyePosition();
// Add some delta so we can lean back afterwards without already being clipped
idVec3 vDelta = newPoint - origPoint;
vDelta.Normalize();
float fLeanTestDelta = 6.0f;
vDelta *= fLeanTestDelta;
// Perform the trace (greebo: use PLAYERSOLID to include player_clip collisions)
trace_t trTest;
gameLocal.clip.TraceBounds( trTest, origPoint, newPoint + vDelta, m_LeanViewBounds, MASK_PLAYERSOLID, player );
bool bWouldClip = trTest.fraction < 1.0f;
//DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Collision trace between old view point ( %d, %d, %d ) and newPoint: ( %d, %d, %d )\r", origPoint.x, origPoint.y, origPoint.z, newPoint.x, newPoint.y, newPoint.z );
// Do not lean farther if the player would hit the wall
if ( bWouldClip )
{
idEntity* traceEnt = gameLocal.GetTraceEntity( trTest );
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING("Lean test point within solid, lean motion stopped.\r" );
if ( traceEnt != NULL )
{
// Door leaning test
if (traceEnt->IsType(CFrobDoor::Type))
{
if ( m_LeanEnt.GetEntity() == NULL ) // not set up yet?
{
CFrobDoor* door = static_cast<CFrobDoor*>(traceEnt);
if ( (m_leanYawAngleDegrees == 0.0f) ||
(m_leanYawAngleDegrees == 180.0f) ||
(m_leanYawAngleDegrees == 90.0f) )
{
if ( !door->IsOpen() )
{
// can the door be listened through?
if ( FindLeanListenPos(trTest.c.point) ) // grayman #4882
{
m_LeanEnt = door;
}
}
}
// grayman #4882 - If the door has a peek entity attached to it, and leaning forward, pass control to the peek code.
// get the peek entity for the door we're leaning on, if it exists
idEntityPtr<idPeek> peekEntity = door->GetDoorPeekEntity();
if ( peekEntity.GetEntity() && (m_leanYawAngleDegrees == 90.0f) )
{
// Process the peek if the player's eyes are close enough to "see through".
idVec3 peekOrigin = peekEntity.GetEntity()->GetPhysics()->GetOrigin(); // grayman #4882
idVec3 playerEyeLocation = player->GetEyePosition(); // grayman #4882
float distFromEye2PeakEntity = (playerEyeLocation - peekOrigin).LengthFast();
if ( distFromEye2PeakEntity < PEEK_MAX_DIST )
{
ProcessPeek(peekEntity.GetEntity(), door, trTest.c.normal);
}
}
}
}
// Detect AI collision
else if (traceEnt->IsType(idAI::Type) )
{
static_cast<idAI*>(traceEnt)->HadTactile(player);
}
else
{
// Is a peek entity nearby? (Non-door situation, like a crack in a wall.)
idPeek* peekEntity = NULL;
if ( traceEnt->IsType(idPeek::Type) ) // did we hit the peek entity directly?
{
peekEntity = static_cast<idPeek*>(traceEnt);
}
else
{
idClip_EntityList entityList;
int num;
num = gameLocal.EntitiesWithinRadius(player->GetEyePosition(), PEEK_MAX_DIST, entityList);
for ( int i = 0 ; i < num ; i++ )
{
idEntity *candidate = entityList[i];
if ( candidate == NULL ) // just in case
{
continue;
}
if ( candidate->IsType(idPeek::Type) )
{
// grayman #4882 - If leaning forward, pass control to the peek code.
// get the peek entity
peekEntity = static_cast<idPeek*>(candidate);
}
}
}
if ( peekEntity && (m_leanYawAngleDegrees == 90.0f) )
{
// Process the peek if the player's eyes are close enough to "see through".
idVec3 peekOrigin = peekEntity->GetPhysics()->GetOrigin(); // grayman #4882
idVec3 playerEyeLocation = player->GetEyePosition(); // grayman #4882
float distFromEye2PeekEntity = (playerEyeLocation - peekOrigin).LengthFast();
if ( distFromEye2PeekEntity < PEEK_MAX_DIST )
{
ProcessPeek(peekEntity, NULL, trTest.c.normal);
// Can listen while in a forward lean
if ( FindLeanListenPos(trTest.c.point) ) // grayman #4882
{
m_LeanEnt = peekEntity;
}
}
}
}
}
return;
}
// Adjust lean angle by delta which was allowed
m_CurrentLeanTiltDegrees += deltaLeanTiltDegrees;
// Update the physics:
UpdateLeanPhysics();
// Log activity
DM_LOG(LC_MOVEMENT, LT_DEBUG)LOGSTRING(
"Lean tilt is now %.2f degrees\r", m_CurrentLeanTiltDegrees
);
}
//----------------------------------------------------------------------
void idPhysics_Player::LeanMove()
{
// Test for leaning immobilization
idPlayer* pPlayer = static_cast<idPlayer*>(self);
if (pPlayer == NULL)
{
DM_LOG(LC_MOVEMENT, LT_ERROR)LOGSTRING("pPlayer is NULL\r");
return;
}
if (pPlayer->GetImmobilization() & EIM_LEAN)
{
// Cancel all leaning
if (m_leanMoveEndTilt > 0.0f)
{
m_leanMoveStartTilt = m_CurrentLeanTiltDegrees;
m_leanTime = cv_pm_lean_time_to_unlean.GetFloat();
m_b_leanFinished = false;
m_leanMoveEndTilt = 0.0f;
}
}
// Change in lean tilt this frame
float deltaLeanTiltDegrees = 0.0;
float newLeanTiltDegrees = 0.0;
if ( !m_b_leanFinished )
{
// Update lean time
m_leanTime -= framemsec;
if (m_leanTime <= 0.0)
{
m_leanTime = 0.0;
m_b_leanFinished = true;
}
// Time ratio
float t = (m_leanMoveEndTilt > 0.0f)
? m_leanTime / cv_pm_lean_time_to_lean.GetFloat()
: m_leanTime / cv_pm_lean_time_to_unlean.GetFloat();
// Cubic bezier params
float p[4] = {0.0f, 0.02f, 0.80f, 1.0f};
// Cubic bezier curve movement
float cb = (pow(1 - t, 3) * p[0])
+ (3 * pow(1 - t, 2) * t * p[1])
+ (3 * (1 - t) * pow(t, 2) * p[2])
+ (pow(t, 3) * p[3]);
cb = 1.0f - cb;
if (m_leanMoveEndTilt > m_leanMoveStartTilt)
newLeanTiltDegrees = (cb * (m_leanMoveEndTilt - m_leanMoveStartTilt)) + m_leanMoveStartTilt;
else if (m_leanMoveStartTilt > m_leanMoveEndTilt)
newLeanTiltDegrees = m_leanMoveStartTilt - (cb * (m_leanMoveStartTilt - m_leanMoveEndTilt));
deltaLeanTiltDegrees = newLeanTiltDegrees - m_CurrentLeanTiltDegrees;
}
// Perform any change to leaning
if (deltaLeanTiltDegrees != 0.0)
{
// Re-orient clip model before change so that collision tests
// are accurate (player may have rotated mid-lean)
UpdateLeanAngle(deltaLeanTiltDegrees);
}
// If player is leaned at all, do an additional clip test and unlean them
// In case they lean and walk into something, or a moveable moves into them, etc.
if (m_CurrentLeanTiltDegrees != 0.0 && TestLeanClip())
{
DM_LOG(LC_MOVEMENT,LT_DEBUG)LOGSTRING("Leaned player clipped solid, unleaning to valid position \r");
UnleanToValidPosition();
}
// Lean door test
if (IsLeaning())
{
UpdateLean();
}
}
bool idPhysics_Player::TestLeanClip()
{
idPlayer *p_player = static_cast<idPlayer*>(self);
// convert proposed angle to a viewpoint in space:
idVec3 vTest = p_player->GetEyePosition();
idVec3 vEyeOffset = -GetGravityNormal()*p_player->EyeHeight();
trace_t trTest;
gameLocal.clip.TraceBounds( trTest, current.origin + vEyeOffset, vTest, m_LeanViewBounds, MASK_SOLID | CONTENTS_BODY, self );
idEntity *TrEnt(NULL);
// Detect AI collision, if entity hit or its bindmaster is an AI:
if( trTest.fraction != 1.0f
&& ( TrEnt = gameLocal.GetTraceEntity( trTest ) ) != NULL
&& TrEnt->IsType(idAI::Type) )
{
static_cast<idAI *>( TrEnt )->HadTactile( (idActor *) self );
}
// Uncomment for debug bounds display
//gameRenderWorld->DebugBounds( colorGreen, m_LeanViewBounds, vTest );
return (trTest.fraction != 1.0f);
}
idVec3 idPhysics_Player::LeanParmsToPoint(float tilt)
{
idPlayer* p_player = static_cast<idPlayer*>(self);
// Set slide distance - adjust for player height
const bool isForwardLean = m_leanYawAngleDegrees == 90.0f;
float standingViewHeight = pm_normalviewheight.GetFloat();
float eyeHeight = p_player->EyeHeight();
float eyeHeightDelta = standingViewHeight - eyeHeight;
float slide = isForwardLean ? 200.0f : cv_pm_lean_slide.GetFloat();
float slideDist = slide - eyeHeightDelta;
// Set lean view angles
float pitchAngle = tilt * idMath::Sin(DEG2RAD(m_leanYawAngleDegrees));
float rollAngle = tilt * idMath::Cos(DEG2RAD(m_leanYawAngleDegrees));
// This will be the point in space relative to the player's eye position
idVec3 vPoint(
slideDist * idMath::Sin(DEG2RAD(-pitchAngle)),
slideDist * idMath::Sin(DEG2RAD(rollAngle)),
0.0
);
// Calculate the z-coordinate of the point, by projecting it
// onto a sphere of radius <slideDist>
vPoint.ProjectSelfOntoSphere(slideDist);
// Subtract the radius, we only need the difference relative to the eyepos
vPoint.z -= slideDist;
// Rotate to player's facing
// this worked for yaw, but had issues with pitch, try something instead
//idMat4 rotMat = viewAngles.ToMat4();
idAngles viewAngNoPitch = viewAngles;
viewAngNoPitch.pitch = 0.0f;
idMat4 rotMat = viewAngNoPitch.ToMat4();
vPoint *= rotMat;
// Sign h4x0rx
vPoint.x *= -1;
vPoint.y *= -1;
// Extract what the player's eye position would be without lean
// Need to do this rather than just adding origin and eye offset due to smoothing
vPoint += p_player->GetEyePosition() - rotMat * m_viewLeanTranslation;
return vPoint;
}
void idPhysics_Player::RopeRemovalCleanup( idEntity *RopeEnt )
{
if( RopeEnt && m_RopeEntity.GetEntity() && m_RopeEntity.GetEntity() == RopeEnt )
m_RopeEntity = NULL;
if( RopeEnt && m_RopeEntTouched.GetEntity() && m_RopeEntTouched.GetEntity() == RopeEnt )
m_RopeEntTouched = NULL;
}
bool idPhysics_Player::CheckPushEntity(idEntity *entity) // grayman #4603
{
return (entity == m_PushForce->GetPushEntity());
}
void idPhysics_Player::ClearPushEntity() // grayman #4603
{
m_PushForce->SetPushEntity(NULL);
}
void idPhysics_Player::UpdateLeanPhysics()
{
idPlayer *p_player = static_cast<idPlayer*>(self);
idAngles viewAngNoPitch = viewAngles;
viewAngNoPitch.pitch = 0;
idMat4 rotPlayerToWorld = viewAngNoPitch.ToMat4();
idMat4 rotWorldToPlayer = rotPlayerToWorld.Transpose();
// unleaned player view origin
idVec3 viewOrig = p_player->GetEyePosition();
// convert angle to a viewpoint in space:
idVec3 newPoint = LeanParmsToPoint( m_CurrentLeanTiltDegrees );
// This is cumbersome, but it lets us extract the smoothed view origin from idPlayer
m_viewLeanTranslation = newPoint - (viewOrig - rotPlayerToWorld * m_viewLeanTranslation);
m_viewLeanTranslation *= rotWorldToPlayer;
const bool isForwardLean = m_leanYawAngleDegrees == 90.0f;
float lean_angle_mod = isForwardLean ? 0.5f : cv_pm_lean_angle_mod.GetFloat();
float angle = m_CurrentLeanTiltDegrees * lean_angle_mod;
m_viewLeanAngles.pitch = angle * idMath::Sin(DEG2RAD(m_leanYawAngleDegrees));
m_viewLeanAngles.roll = angle * idMath::Cos(DEG2RAD(m_leanYawAngleDegrees));
}
float idPhysics_Player::GetDeltaViewYaw( void )
{
return m_DeltaViewYaw;
}
float idPhysics_Player::GetDeltaViewPitch( void )
{
return m_DeltaViewPitch;
}
bool idPhysics_Player::IsMidAir() const
{
return m_bMidAir;
}
void idPhysics_Player::UpdateLeanedInputYaw( idAngles &InputAngles )
{
if (!IsLeaning()) return; // nothing to do
/**
* Leaned view yaw check for clipping
**/
// Have a delta so that we don't get stuck on the wall due to floating point errors
float AddedYawDelt = 4.0f; // amount to check ahead of the yaw change, in degrees
float TestDeltaYaw = idMath::AngleNormalize180( InputAngles.yaw - viewAngles.yaw );
// Add delta
if( TestDeltaYaw < 0.0f )
TestDeltaYaw -= AddedYawDelt;
else
TestDeltaYaw += AddedYawDelt;
idPlayer* player = static_cast<idPlayer*>(self);
idVec3 vEyeOffset = -GetGravityNormal() * player->EyeHeight();
// make the test bounds go back to the unleaned eye point
idBounds ViewBoundsExp = m_LeanViewBounds;
ViewBoundsExp.AddPoint( -m_viewLeanTranslation );
idClipModel ViewClip( ViewBoundsExp );
idAngles viewAngYaw;
viewAngYaw.Zero();
viewAngYaw.yaw = viewAngles.yaw;
ViewClip.SetPosition( player->GetEyePosition(), viewAngYaw.ToMat3() );
//idRotation ViewYawRot( current.origin, -GetGravityNormal(), TestDeltaYaw );
idRotation ViewYawRot( current.origin, GetGravityNormal(), TestDeltaYaw );
idVec3 startPoint = player->GetEyePosition();
idVec3 endPoint = startPoint;
ViewYawRot.RotatePoint( endPoint );
trace_t TrResults;
gameLocal.clip.Rotation( TrResults, startPoint, ViewYawRot, &ViewClip, ViewClip.GetAxis(), MASK_SOLID | CONTENTS_BODY, self );
DM_LOG(LC_MOVEMENT,LT_DEBUG)LOGSTRING("Leaned View Yaw Test: Original viewpoint (%f, %f, %f) Tested viewpoint: (%f, %f, %f) \r", startPoint.x, startPoint.y, startPoint.z, endPoint.x, endPoint.y, endPoint.z );
// Cancel rotation if check-ahead rotation trace fails
if( TrResults.fraction != 1.0f )
{
DM_LOG(LC_MOVEMENT,LT_DEBUG)LOGSTRING("Leaned View Yaw Test: Clipped with rotation trace fraction %f. Delta yaw not allowed \r", TrResults.fraction );
InputAngles.yaw = viewAngles.yaw;
idEntity* TrEnt = gameLocal.GetTraceEntity( TrResults );
// Detect AI collision, if entity hit or its bindmaster is an AI:
if( TrEnt != NULL && TrEnt->IsType(idAI::Type) )
{
static_cast<idAI *>( TrEnt )->HadTactile( static_cast<idActor*>(self) );
}
return;
}
// debug draw the test clip model
/*
collisionModelManager->DrawModel( ViewClip.Handle(), ViewClip.GetOrigin(),
ViewClip.GetAxis(), vec3_origin, 0.0f );
*/
}
void idPhysics_Player::UnleanToValidPosition( void )
{
trace_t trTest;
idVec3 vTest(vec3_zero);
idPlayer *p_player = (idPlayer *) self;
idVec3 vEyeOffset = -GetGravityNormal()*p_player->EyeHeight();
float TestLeanDegrees = m_CurrentLeanTiltDegrees;
float DeltaDeg = TestLeanDegrees / (float) cv_pm_lean_to_valid_increments.GetInteger();
// Must temporarily set these to get proper behavior from max angle
m_leanMoveMaxAngle = m_CurrentLeanTiltDegrees;
m_leanMoveStartTilt = m_CurrentLeanTiltDegrees;
m_leanMoveEndTilt = 0.0f;
for( int i=0; i < cv_pm_lean_to_valid_increments.GetInteger(); i++ )
{
// Lean degrees are always positive
TestLeanDegrees -= DeltaDeg;
// convert proposed angle to a viewpoint in world space:
vTest = LeanParmsToPoint( TestLeanDegrees );
gameLocal.clip.TraceBounds( trTest, current.origin + vEyeOffset, vTest, m_LeanViewBounds, MASK_SOLID | CONTENTS_BODY, self );
// break if valid point was found
if( trTest.fraction == 1.0f )
break;
}
// transform lean parameters with final answer
m_CurrentLeanTiltDegrees = TestLeanDegrees;
UpdateLeanPhysics();
}
bool idPhysics_Player::FindLeanListenPos(const idVec3& incidencePoint) // grayman #4882
{
bool bFoundEmptySpace( false );
int contents = -1;
idVec3 vTest(incidencePoint), vDirTest(0,0,0), vLeanDir( 1.0f, 0.0f, 0.0f );
idAngles LeanYaw;
idAngles viewYawOnly = viewAngles;
LeanYaw.Zero();
LeanYaw.yaw = m_leanYawAngleDegrees - 90.0f;
viewYawOnly.pitch = 0.0f;
viewYawOnly.roll = 0.0f;
vDirTest = viewYawOnly.ToMat3() * LeanYaw.ToMat3() * vLeanDir;
vDirTest.Normalize();
int MaxCount = cv_pm_lean_door_increments.GetInteger();
for( int count = 1; count < MaxCount; count++ )
{
vTest += vDirTest * ( (float) count / (float) MaxCount ) * cv_pm_lean_door_max.GetFloat();
contents = gameLocal.clip.Contents( vTest, NULL, mat3_identity, CONTENTS_SOLID, self );
// found empty space on other side of door
if( !( (contents & MASK_SOLID) > 0 ) )
{
DM_LOG(LC_MOVEMENT,LT_DEBUG)LOGSTRING("Lean Into Door: Found empty space on other side of door. Incidence point: %s Empty space point: %s \r", incidencePoint.ToString(), vTest.ToString() );
bFoundEmptySpace = true;
m_LeanListenPos = vTest;
break;
}
}
// uncomment to debug the lean direction line
// gameRenderWorld->DebugArrow( colorBlue, IncidencePoint, IncidencePoint + 15.0f * vDirTest, 5.0f, 10000 );
return bFoundEmptySpace;
}
void idPhysics_Player::UpdateLean( void ) // grayman #4882 - expanded to handle wall cracks
{
idPlayer* player = static_cast<idPlayer*>(self);
// If there's a peek entity, is it bound to a door?
idEntity* e = m_LeanEnt.GetEntity();
// Are we leaning against a door?
if ( e && e->IsType(CFrobDoor::Type) )
{
CFrobDoor* door = static_cast<CFrobDoor*>(e);
if ( player )
{
if ( !m_LeanEnt.IsValid() || door->IsOpen() || !IsLeaning() )
{
m_LeanEnt = NULL;
return;
}
idBounds TestBounds = m_LeanViewBounds;
TestBounds.ExpandSelf(cv_pm_lean_door_bounds_exp.GetFloat());
TestBounds.TranslateSelf(player->GetEyePosition());
/** More precise test (Not currently used)
int numEnts = 0;
idEntity *ent = NULL;
bool bMatchedDoor(false);
idClip_EntityList ents;
numEnts = gameLocal.clip.EntitiesTouchingBounds( TestBounds, CONTENTS_SOLID, ents );
for( int i=0; i < numEnts; i++ )
{
if( ents[i] == (idEntity *) door )
{
bMatchedDoor = true;
break;
}
}
if( !bMatchedDoor )
{
m_LeanEnt = NULL;
goto Quit;
}
**/
if ( !TestBounds.IntersectsBounds(door->GetPhysics()->GetAbsBounds()) )
{
m_LeanEnt = NULL;
return;
}
// We are leaning into a door
// overwrite the current player listener loc with that calculated for the door
player->SetListenLoc(m_LeanListenPos);
}
}
else if ( e && e->IsType(idPeek::Type) )// are we leaning near a peek entity that isn't a keyhole?
{
if ( player )
{
if ( !m_LeanEnt.IsValid() || !IsLeaning() )
{
m_LeanEnt = NULL;
return;
}
idBounds TestBounds = m_LeanViewBounds;
TestBounds.ExpandSelf(cv_pm_lean_door_bounds_exp.GetFloat());
TestBounds.TranslateSelf(player->GetEyePosition());
if ( !TestBounds.IntersectsBounds(e->GetPhysics()->GetAbsBounds()) )
{
m_LeanEnt = NULL;
return;
}
player->SetListenLoc(m_LeanListenPos);
}
}
// Obsttorte: #5899
else
{
if (player)
{
m_LeanEnt = NULL;
player->SetListenLoc(vec3_zero);
}
}
}
bool idPhysics_Player::IsPeekLeaning( void )
{
return (m_LeanEnt.GetEntity() != NULL) && m_LeanEnt.IsValid();
}
idStr idPhysics_Player::GetClimbSurfaceType() const
{
return m_bOnClimb ? m_ClimbSurfName : "";
}
float idPhysics_Player::GetClimbLateralCoord(const idVec3& origVec) const
{
if (m_bOnClimb)
{
idVec3 orig = origVec - (origVec * gravityNormal) * gravityNormal;
idVec3 climbNormXY = m_vClimbNormal - (m_vClimbNormal * gravityNormal) * gravityNormal;
idVec3 latNormal = climbNormXY.Cross(gravityNormal);
latNormal.NormalizeFast();
return orig * latNormal;
}
return 0.0f;
}
void idPhysics_Player::SetRefEntVel( idEntity *ent, int bodID)
{
idVec3 EntCOM, ThisCOM, r, AngVel;
idMat3 DummyMat;
float dummy;
if( !ent )
{
m_RefEntVelocity.Zero();
}
else
{
idPhysics *phys = ent->GetPhysics();
// Sometimes we have statics bound to movers, and the static doesn't return a velocity
if( phys->IsType(idPhysics_Static::Type)
|| phys->IsType(idPhysics_StaticMulti::Type) )
{
if( ent->GetBindMaster() )
{
phys = ent->GetBindMaster()->GetPhysics();
// TODO: Replace this with ent->bindbody
bodID = 0;
DM_LOG(LC_MOVEMENT,LT_DEBUG)LOGSTRING("SetRefVel switched from ent %s to bindmaster %s\r", ent->name.c_str(), ent->GetBindMaster()->name.c_str() );
}
}
m_RefEntVelocity = phys->GetLinearVelocity( bodID );
// Apparently this is needed for moveables riding on movers:
m_RefEntVelocity += phys->GetPushedLinearVelocity( bodID );
// Add orbit velocity from angular vel of mover
// v_tangential = omega X r, where r is the distance between COM
AngVel = phys->GetAngularVelocity( bodID );
AngVel += phys->GetPushedAngularVelocity( bodID );
// Find our center of mass:
GetClipModel()->GetMassProperties( 1.0f, dummy, ThisCOM, DummyMat );
ThisCOM = GetOrigin() + ThisCOM * GetAxis();
// Find entity center of mass
if( phys->GetClipModel( bodID)->IsTraceModel() )
{
phys->GetClipModel( bodID )->GetMassProperties( 1.0f, dummy, EntCOM, DummyMat );
EntCOM = phys->GetOrigin( bodID ) + EntCOM * phys->GetAxis( bodID );
}
else
EntCOM = phys->GetOrigin( bodID );
// r = ThisCOM - EntCOM;
r = GetOrigin() - phys->GetOrigin( bodID );
// Linear component due to angular velocity commented out for now
// Is not correct and tends to kill the player.
// m_RefEntVelocity += AngVel.Cross( r );
// Uncomment for debugging
// DM_LOG(LC_MOVEMENT,LT_DEBUG)LOGSTRING("SetRefEntVelocity: Ent angular velocity is %s, Ent COM is %s, player COM is %s, added linear velocity due to angular rotation of ent is: %s\r",AngVel.ToString(),EntCOM.ToString(),ThisCOM.ToString(),AngVel.Cross(r).ToString() );
}
}
idVec3 idPhysics_Player::GetRefEntVel( void ) const
{
return m_RefEntVelocity;
}
int idPhysics_Player::GetMovementFlags( void )
{
return current.movementFlags;
}
void idPhysics_Player::SetMovementFlags( int flags )
{
current.movementFlags = flags;
}
void idPhysics_Player::StartShouldering(idEntity const * const pBody)
{
if (cv_pm_shoulderAnim_msecs.GetFloat() <= 0.0f)
return;
// Initialize
if (m_eShoulderAnimState == eShoulderingAnimation_NotStarted)
{
if (pBody == nullptr)
{
DM_LOG(LC_MOVEMENT, LT_ERROR)LOGSTRING("Shouldering: pBody is NULL\r");
return;
}
idPlayer* pPlayer = static_cast<idPlayer*>(self);
if (pPlayer == nullptr)
{
DM_LOG(LC_MOVEMENT, LT_ERROR)LOGSTRING("Shouldering: pPlayer is NULL\r");
return;
}
static const int iImmobilization =
EIM_CLIMB | EIM_ITEM_SELECT | EIM_WEAPON_SELECT | EIM_ATTACK | EIM_ITEM_USE
| EIM_MANTLE | EIM_FROB_COMPLEX | EIM_MOVEMENT | EIM_CROUCH_HOLD
| EIM_CROUCH | EIM_JUMP | EIM_FROB | EIM_FROB_HILIGHT | EIM_LEAN;
pPlayer->SetImmobilization("ShoulderingAnimation", iImmobilization);
// Check height of body: If heigher than crouched, do not go to crouched state
const float fBodyHeight = pBody->GetPhysics()->GetOrigin() * (-gravityNormal);
const float fCrouchedHeight = GetOrigin() * (-gravityNormal) + pm_crouchviewheight.GetFloat();
m_bShouldering_SkipDucking = fBodyHeight > fCrouchedHeight || cv_pm_shoulderAnim_delay_msecs.GetFloat() <= 0.0f;
// Get rustle sound to play while shouldering
const idKeyValue* const pKeyValue = pBody->spawnArgs.FindKey("snd_rustle");
if (pKeyValue != nullptr)
{
pPlayer->spawnArgs.Set("snd_shouldering_rustle", pKeyValue->GetValue());
m_fShouldering_TimeToNextSound = 0.0f;
}
else
{
// No appropriate rustle sound found. Skip playing rustle sound
m_fShouldering_TimeToNextSound = FLT_MAX;
pPlayer->spawnArgs.Set("snd_shouldering_rustle", "");
}
m_eShoulderAnimState = eShoulderingAnimation_Initialized;
}
StartShoulderingAnim();
}
bool idPhysics_Player::IsShouldering() const
{
return m_eShoulderAnimState == eShoulderingAnimation_Active;
}
void idPhysics_Player::StartShoulderingAnim()
{
// Try starting the animation
if (m_eShoulderAnimState == eShoulderingAnimation_Initialized
|| m_eShoulderAnimState == eShoulderingAnimation_Scheduled)
{
if (IsLeaning())
{
// Wait for unlean first
m_eShoulderAnimState = eShoulderingAnimation_Scheduled;
}
else
{
// Start animation right away
m_fShoulderingTime = cv_pm_shoulderAnim_msecs.GetFloat();
m_fPrevShoulderingPitchOffset = 0.0f;
m_PrevShoulderingPosOffset = vec3_zero;
m_ShoulderingStartPosRelative = GetOrigin();
// #6259: Make shouldering start pos relative to potentially moving ground entity
idEntity* groundEnt = groundEntityPtr.GetEntity();
if (groundEnt != NULL)
{
m_pShoulderingGroundEntity = groundEnt;
idPhysics* p_physics = groundEnt->GetPhysics();
if (p_physics != NULL)
{
m_ShoulderingStartPosRelative =
(m_ShoulderingStartPosRelative - p_physics->GetOrigin()) * p_physics->GetAxis().Transpose();
}
}
m_ShoulderingCurrentPosRelative = m_ShoulderingStartPosRelative;
m_eShoulderAnimState = eShoulderingAnimation_Active;
if (!m_bShouldering_SkipDucking && !IsCrouching())
{
current.movementFlags |= PMF_DUCKED;
// Reserve some additional time for going to ducked state
// before playing the animation
m_fShoulderingTime += cv_pm_shoulderAnim_delay_msecs.GetFloat();
}
}
}
}
void idPhysics_Player::ShoulderingMove()
{
if (m_eShoulderAnimState != eShoulderingAnimation_Active)
return;
idPlayer* pPlayer = static_cast<idPlayer*>(self);
if (pPlayer == nullptr)
{
DM_LOG(LC_MOVEMENT, LT_ERROR)LOGSTRING("ShoulderingMove: pPlayer is NULL\r");
// Cancel
static_cast<idPlayer*>(self)->SetImmobilization("ShoulderingAnimation", 0);
m_eShoulderAnimState = eShoulderingAnimation_NotStarted;
return;
}
// Are we allowed to play the view animation already?
if (m_fShoulderingTime <= cv_pm_shoulderAnim_msecs.GetFloat())
{
// Play a rustle sound if the time is up
if (m_fShouldering_TimeToNextSound <= 0.0f)
{
const idKeyValue* const pKeyValue = pPlayer->spawnArgs.FindKey("snd_shouldering_rustle");
if (pKeyValue != nullptr && pKeyValue->GetValue().Length() > 0)
{
int iLength_ms = 0;
pPlayer->StartSound("snd_shouldering_rustle", SND_CHANNEL_ANY, SSF_GLOBAL, 0, &iLength_ms);
m_fShouldering_TimeToNextSound = static_cast<float>(iLength_ms) * 0.5 * (1 + gameLocal.random.RandomFloat());
}
else
m_fShouldering_TimeToNextSound = FLT_MAX;
}
// Compute view angles and position for lean
const float fTimeRadians = idMath::PI * m_fShoulderingTime / cv_pm_shoulderAnim_msecs.GetFloat();
idVec3 newPositionOffset = (idMath::Sin(fTimeRadians) * cv_pm_shoulderAnim_rockDist.GetFloat()) * viewForward;
const float fPitchOffset = idMath::Sin(fTimeRadians) * cv_pm_shoulderAnim_rockDist.GetFloat();
// Add vertical dip animation
const float fAbsoluteDipDuration =
cv_pm_shoulderAnim_dip_duration.GetFloat()*cv_pm_shoulderAnim_msecs.GetFloat();
const float fDipStart =
cv_pm_shoulderAnim_msecs.GetFloat()*0.5f + fAbsoluteDipDuration * 0.5f;
const float fDipEnd =
cv_pm_shoulderAnim_msecs.GetFloat()*0.5f - fAbsoluteDipDuration * 0.5f;
if (m_fShoulderingTime >= fDipEnd && m_fShoulderingTime < fDipStart)
{
const float fDipTimeRadians = idMath::PI * (fDipEnd - m_fShoulderingTime) / fAbsoluteDipDuration;
newPositionOffset += (-idMath::Sin(fDipTimeRadians) * cv_pm_shoulderAnim_dip_dist.GetFloat()) * gravityNormal;
}
// Apply animation to player position and view angle
idVec3 newPosition = m_ShoulderingCurrentPosRelative + (newPositionOffset - m_PrevShoulderingPosOffset);
m_ShoulderingCurrentPosRelative = newPosition;
m_PrevShoulderingPosOffset = newPositionOffset;
if (m_pShoulderingGroundEntity != NULL)
{
// #6259: Shouldering animation must be relative to potentially moving ground entity
idPhysics* p_physics = m_pShoulderingGroundEntity->GetPhysics();
if (p_physics != NULL)
{
newPosition = p_physics->GetOrigin() + p_physics->GetAxis() * newPosition;
}
}
SetOrigin(newPosition);
viewAngles.pitch += (fPitchOffset - m_fPrevShoulderingPitchOffset);
m_fPrevShoulderingPitchOffset = fPitchOffset;
pPlayer->SetViewAngles(viewAngles);
}
// Are we done?
if (m_fShoulderingTime == 0.0f) // We explicitly set 0.0f below, so equality check with 0.0f is ok.
{
static_cast<idPlayer*>(self)->SetImmobilization("ShoulderingAnimation", 0);
m_eShoulderAnimState = eShoulderingAnimation_NotStarted;
// Explicitly return to start position to avoid clipping due to quantization errors
idVec3 endPos = m_ShoulderingStartPosRelative;
if (m_pShoulderingGroundEntity != NULL)
{
// #6259: Shouldering animation must be relative to potentially moving ground entity
idPhysics* p_physics = m_pShoulderingGroundEntity->GetPhysics();
if (p_physics != NULL)
{
endPos = p_physics->GetOrigin() + p_physics->GetAxis() * endPos;
}
m_pShoulderingGroundEntity = NULL;
}
SetOrigin(endPos);
return;
}
// Update animation timer
m_fShouldering_TimeToNextSound -= framemsec;
m_fShoulderingTime -= framemsec;
if (m_fShoulderingTime < 0.0f)
m_fShoulderingTime = 0.0f;
}
|
|
|
Of course if you say "fix me this catapult launch", it just goes to global code and fixes it for one specific catapult launch, with appropriate comments as if the rest of the game does not exist =) I don't understand: 1) Why disable damage for upwards collisions with ground? 2) What are the consequences for the other cases? I'd prefer you ask your AI how to change the map in such a way that the player gets fixed velocity regardless of physics engine. Instead of hacking the physics engine. |
|
|
1) When testing the changes, the upward movement triggered the crashland condition to kill the player. Might be able to remove that change now that the upward motion has further fixes since it was suggested. Honestly unsure how the original map didn't kill the player, the launcher certainly looks fast enough to do that. 2) In theory, if we have any other physics puzzles involving movers that rely on the 60FPS behavior this should fix those too. Off the top of my head I can't think of any other cases. There were also fixes for the player getting snagged inside of the mover due to timing causing the collision locations to intersect the player foot location. Just a little experiment to see what it would come up with. |
|
|
The main problem was always that player does not fly through the intended trajectory. That's why I suggest setting velocity in a more reliably way. I'm pretty sure landing damage can be fixed easily after that. I'd if the player is pushed with a rapid mover, dying is a relatively intended behavior which does not require fixing =) |
|
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 23.03.2025 20:30 | stgatilov | New Issue | |
| 23.03.2025 20:31 | stgatilov | Description Updated | |
| 23.03.2025 20:31 | stgatilov | Additional Information Updated | |
| 23.02.2026 23:30 | nbohr1more | Target Version | TDM 2.14 => TDM 2.15 |
| 07.11.2026 02:02 | nbohr1more | Note Added: 0017388 | |
| 07.11.2026 02:02 | nbohr1more | File Added: Push.cpp | |
| 07.11.2026 02:02 | nbohr1more | File Added: Physics_Player.cpp | |
| 07.11.2026 02:03 | nbohr1more | Assigned To | => stgatilov |
| 07.11.2026 02:03 | nbohr1more | Status | new => feedback |
| 07.11.2026 02:32 | nbohr1more | File Deleted: Push.cpp | |
| 07.11.2026 02:32 | nbohr1more | File Deleted: Physics_Player.cpp | |
| 07.11.2026 03:22 | nbohr1more | File Deleted: Physics_Player.cpp | |
| 07.11.2026 03:23 | nbohr1more | Note Added: 0017390 | |
| 07.11.2026 03:23 | nbohr1more | File Added: Physics_Player.cpp | |
| 07.11.2026 03:23 | nbohr1more | File Added: Player.cpp | |
| 08.11.2026 10:45 | stgatilov | Note Added: 0017391 | |
| 08.11.2026 18:16 | nbohr1more | Note Added: 0017392 | |
| 08.11.2026 18:42 | stgatilov | Note Added: 0017393 |