2019-10-01 21:45:39 +02:00
# include <sourcemod>
# include <sdkhooks>
# include <sdktools>
# include <CSSFixes>
# include <dhooks>
# pragma semicolon 1
# pragma newdecls required
public Plugin myinfo =
{
name = " LagCompensation " ,
author = " BotoX " ,
description = " " ,
2019-10-16 15:18:09 +02:00
version = " 0.1 " ,
2019-10-01 21:45:39 +02:00
url = " "
} ;
bool g_bLateLoad = false ;
2019-10-10 15:38:58 +02:00
// Don't change this.
# define MAX_EDICTS 2048
2019-10-02 18:54:19 +02:00
# define MAX_RECORDS 32
2019-10-20 12:44:34 +02:00
# define MAX_ENTITIES 128
2019-10-02 23:28:20 +02:00
//#define DEBUG
2019-10-01 21:45:39 +02:00
enum struct LagRecord
{
float vecOrigin [ 3 ] ;
float vecAngles [ 3 ] ;
2019-10-06 20:24:43 +02:00
float flSimulationTime ;
2019-10-01 21:45:39 +02:00
}
enum struct EntityLagData
{
int iEntity ;
int iRecordIndex ;
int iNumRecords ;
2019-10-02 23:28:20 +02:00
int iRecordsValid ;
2019-10-10 15:38:58 +02:00
int iSpawned ;
2019-10-02 23:28:20 +02:00
int iDeleted ;
2019-10-03 12:02:23 +02:00
int iNotMoving ;
2019-10-08 00:00:19 +02:00
int iTouchStamp ;
2019-10-01 21:45:39 +02:00
bool bRestore ;
2019-10-20 12:44:34 +02:00
bool bLateKill ;
2019-10-01 21:45:39 +02:00
LagRecord RestoreData ;
}
LagRecord g_aaLagRecords [ MAX_ENTITIES ] [ MAX_RECORDS ] ;
EntityLagData g_aEntityLagData [ MAX_ENTITIES ] ;
int g_iNumEntities = 0 ;
2019-10-05 15:17:10 +02:00
bool g_bCleaningUp = false ;
2019-10-01 21:45:39 +02:00
2019-10-02 23:28:20 +02:00
Handle g_hGetAbsOrigin ;
2019-10-02 18:54:19 +02:00
Handle g_hSetAbsOrigin ;
2019-10-06 20:24:43 +02:00
Handle g_hSetLocalAngles ;
2019-10-01 21:45:39 +02:00
2019-10-02 23:28:20 +02:00
Handle g_hUTIL_Remove ;
2019-10-03 22:09:53 +02:00
Handle g_hRestartRound ;
2019-11-01 15:58:14 +01:00
Handle g_hSetTarget ;
2019-11-02 01:35:20 +01:00
Handle g_hSetTargetPost ;
2019-10-03 22:09:53 +02:00
2019-10-10 15:38:58 +02:00
char g_aBlockTriggerTouch [ MAX_EDICTS ] = { 0 , . . . } ;
2019-10-20 12:44:34 +02:00
char g_aaBlockTouch [ MAXPLAYERS + 1 ] [ MAX_EDICTS ] ;
2019-10-02 23:28:20 +02:00
2019-10-01 21:45:39 +02:00
public void OnPluginStart ( )
{
Handle hGameData = LoadGameConfigFile ( " LagCompensation.games " ) ;
if ( ! hGameData )
SetFailState ( " Failed to load LagCompensation gamedata. " ) ;
2019-10-02 23:28:20 +02:00
// CBaseEntity::GetAbsOrigin
StartPrepSDKCall ( SDKCall_Entity ) ;
if ( ! PrepSDKCall_SetFromConf ( hGameData , SDKConf_Signature , " GetAbsOrigin " ) )
{
delete hGameData ;
SetFailState ( " PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, \" GetAbsOrigin \" ) failed! " ) ;
}
PrepSDKCall_SetReturnInfo ( SDKType_Vector , SDKPass_ByRef ) ;
g_hGetAbsOrigin = EndPrepSDKCall ( ) ;
2019-10-02 18:54:19 +02:00
// CBaseEntity::SetAbsOrigin
2019-10-01 21:45:39 +02:00
StartPrepSDKCall ( SDKCall_Entity ) ;
2019-10-02 18:54:19 +02:00
if ( ! PrepSDKCall_SetFromConf ( hGameData , SDKConf_Signature , " SetAbsOrigin " ) )
2019-10-01 21:45:39 +02:00
{
delete hGameData ;
2019-10-02 18:54:19 +02:00
SetFailState ( " PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, \" SetAbsOrigin \" ) failed! " ) ;
2019-10-01 21:45:39 +02:00
}
PrepSDKCall_AddParameter ( SDKType_Vector , SDKPass_ByRef ) ;
2019-10-02 18:54:19 +02:00
g_hSetAbsOrigin = EndPrepSDKCall ( ) ;
2019-10-01 21:45:39 +02:00
2019-10-06 20:24:43 +02:00
// CBaseEntity::SetLocalAngles
2019-10-01 21:45:39 +02:00
StartPrepSDKCall ( SDKCall_Entity ) ;
2019-10-06 20:24:43 +02:00
if ( ! PrepSDKCall_SetFromConf ( hGameData , SDKConf_Signature , " SetLocalAngles " ) )
2019-10-01 21:45:39 +02:00
{
delete hGameData ;
2019-10-06 20:24:43 +02:00
SetFailState ( " PrepSDKCall_SetFromConf(hGameData, SDKConf_Signature, \" SetLocalAngles \" ) failed! " ) ;
2019-10-01 21:45:39 +02:00
}
PrepSDKCall_AddParameter ( SDKType_QAngle , SDKPass_ByRef ) ;
2019-10-06 20:24:43 +02:00
g_hSetLocalAngles = EndPrepSDKCall ( ) ;
2019-10-01 21:45:39 +02:00
2019-10-03 22:09:53 +02:00
// ::UTIL_Remove
2019-10-02 23:28:20 +02:00
g_hUTIL_Remove = DHookCreateFromConf ( hGameData , " UTIL_Remove " ) ;
if ( ! g_hUTIL_Remove )
{
delete hGameData ;
SetFailState ( " Failed to setup detour for UTIL_Remove " ) ;
}
if ( ! DHookEnableDetour ( g_hUTIL_Remove , false , Detour_OnUTIL_Remove ) )
2019-10-03 22:09:53 +02:00
{
delete hGameData ;
2019-10-02 23:28:20 +02:00
SetFailState ( " Failed to detour UTIL_Remove. " ) ;
2019-10-03 22:09:53 +02:00
}
2019-10-02 23:28:20 +02:00
2019-10-03 22:09:53 +02:00
// CCSGameRules::RestartRound
g_hRestartRound = DHookCreateFromConf ( hGameData , " CCSGameRules__RestartRound " ) ;
if ( ! g_hRestartRound )
{
delete hGameData ;
SetFailState ( " Failed to setup detour for CCSGameRules__RestartRound " ) ;
}
2019-10-02 23:28:20 +02:00
2019-10-03 22:09:53 +02:00
if ( ! DHookEnableDetour ( g_hRestartRound , false , Detour_OnRestartRound ) )
2019-10-05 15:17:10 +02:00
{
delete hGameData ;
2019-10-03 22:09:53 +02:00
SetFailState ( " Failed to detour CCSGameRules__RestartRound. " ) ;
2019-10-05 15:17:10 +02:00
}
2019-10-10 15:38:58 +02:00
2019-11-01 15:58:14 +01:00
// CLogicMeasureMovement::SetTarget
g_hSetTarget = DHookCreateFromConf ( hGameData , " CLogicMeasureMovement__SetTarget " ) ;
if ( ! g_hSetTarget )
{
delete hGameData ;
SetFailState ( " Failed to setup detour for CLogicMeasureMovement__SetTarget " ) ;
}
2019-11-02 01:35:20 +01:00
if ( ! DHookEnableDetour ( g_hSetTarget , false , Detour_OnSetTargetPre ) )
2019-11-01 15:58:14 +01:00
{
delete hGameData ;
SetFailState ( " Failed to detour CLogicMeasureMovement__SetTarget. " ) ;
}
2019-11-02 01:35:20 +01:00
// CLogicMeasureMovement::SetTarget (fix post hook crashing due to this pointer being overwritten)
g_hSetTargetPost = DHookCreateFromConf ( hGameData , " CLogicMeasureMovement__SetTarget_post " ) ;
if ( ! g_hSetTargetPost )
{
delete hGameData ;
SetFailState ( " Failed to setup detour for CLogicMeasureMovement__SetTarget_post " ) ;
}
if ( ! DHookEnableDetour ( g_hSetTargetPost , true , Detour_OnSetTargetPost ) )
{
delete hGameData ;
SetFailState ( " Failed to detour CLogicMeasureMovement__SetTarget_post. " ) ;
}
2019-10-05 15:17:10 +02:00
delete hGameData ;
2019-10-10 15:38:58 +02:00
RegAdminCmd ( " sm_unlag " , Command_AddLagCompensation , ADMFLAG_RCON , " sm_unlag <entidx> " ) ;
2019-10-06 20:24:43 +02:00
RegAdminCmd ( " sm_lagged " , Command_CheckLagCompensated , ADMFLAG_GENERIC , " sm_lagged " ) ;
2019-10-05 15:17:10 +02:00
2019-10-20 12:44:34 +02:00
FilterClientEntityMap ( g_aaBlockTouch , true ) ;
2019-10-02 23:28:20 +02:00
}
2019-10-05 15:17:10 +02:00
public Action Command_AddLagCompensation ( int client , int argc )
{
if ( argc < 1 )
{
2019-10-20 12:44:34 +02:00
ReplyToCommand ( client , " [SM] Usage: sm_unlag <entidx> [late] " ) ;
2019-10-05 15:17:10 +02:00
return Plugin_Handled ;
}
char sArgs [ 32 ] ;
GetCmdArg ( 1 , sArgs , sizeof ( sArgs ) ) ;
int entity = StringToInt ( sArgs ) ;
2019-10-20 12:44:34 +02:00
bool late = false ;
if ( argc > = 2 )
{
GetCmdArg ( 2 , sArgs , sizeof ( sArgs ) ) ;
late = view_as < bool > ( StringToInt ( sArgs ) ) ;
}
AddEntityForLagCompensation ( entity , late ) ;
2019-10-10 15:38:58 +02:00
g_aBlockTriggerTouch [ entity ] = 1 ;
2019-10-05 15:17:10 +02:00
return Plugin_Handled ;
}
2019-10-02 23:28:20 +02:00
2019-10-06 20:24:43 +02:00
public Action Command_CheckLagCompensated ( int client , int argc )
{
for ( int i = 0 ; i < g_iNumEntities ; i + + )
{
char sClassname [ 64 ] ;
GetEntityClassname ( g_aEntityLagData [ i ] . iEntity , sClassname , sizeof ( sClassname ) ) ;
char sTargetname [ 64 ] ;
GetEntPropString ( g_aEntityLagData [ i ] . iEntity , Prop_Data , " m_iName " , sTargetname , sizeof ( sTargetname ) ) ;
int iHammerID = GetEntProp ( g_aEntityLagData [ i ] . iEntity , Prop_Data , " m_iHammerID " ) ;
PrintToConsole ( client , " %2d. #%d %s \" %s \" (#%d) " , i , g_aEntityLagData [ i ] . iEntity , sClassname , sTargetname , iHammerID ) ;
}
2019-10-10 15:38:58 +02:00
for ( int i = 0 ; i < MAX_EDICTS ; i + + )
2019-10-06 20:24:43 +02:00
{
bool bDeleted = false ;
for ( int j = 1 ; j < = MaxClients ; j + + )
{
2019-10-20 12:44:34 +02:00
if ( g_aaBlockTouch [ j ] [ i ] )
2019-10-06 20:24:43 +02:00
{
bDeleted = true ;
break ;
}
}
2019-10-10 15:38:58 +02:00
if ( g_aBlockTriggerTouch [ i ] | | bDeleted )
2019-10-06 20:24:43 +02:00
{
int index = - 1 ;
for ( int j = 0 ; j < g_iNumEntities ; j + + )
{
if ( g_aEntityLagData [ j ] . iEntity = = i )
{
index = j ;
break ;
}
}
char sClassname [ 64 ] = " INVALID " ;
char sTargetname [ 64 ] = " INVALID " ;
int iHammerID = - 1 ;
if ( IsValidEntity ( i ) )
{
GetEntityClassname ( i , sClassname , sizeof ( sClassname ) ) ;
GetEntPropString ( i , Prop_Data , " m_iName " , sTargetname , sizeof ( sTargetname ) ) ;
iHammerID = GetEntProp ( i , Prop_Data , " m_iHammerID " ) ;
}
2019-10-10 15:38:58 +02:00
bool bBlockPhysics = g_aBlockTriggerTouch [ i ] ;
2019-10-06 20:24:43 +02:00
PrintToConsole ( client , " %2d. #%d %s \" %s \" (#%d) -> BlockPhysics: %d / Deleted: %d " , index , i , sClassname , sTargetname , iHammerID , bBlockPhysics , bDeleted ) ;
}
}
return Plugin_Handled ;
}
2019-10-03 22:09:53 +02:00
public void OnPluginEnd ( )
{
2019-10-05 15:17:10 +02:00
g_bCleaningUp = true ;
2019-10-20 12:44:34 +02:00
FilterClientEntityMap ( g_aaBlockTouch , false ) ;
2019-10-10 15:38:58 +02:00
FilterTriggerTouchPlayers ( g_aBlockTriggerTouch , false ) ;
2019-10-04 11:23:05 +02:00
DHookDisableDetour ( g_hUTIL_Remove , false , Detour_OnUTIL_Remove ) ;
2019-10-03 12:02:23 +02:00
for ( int i = 0 ; i < g_iNumEntities ; i + + )
2019-10-02 23:28:20 +02:00
{
2019-10-03 22:09:53 +02:00
if ( ! IsValidEntity ( g_aEntityLagData [ i ] . iEntity ) )
continue ;
2019-10-03 12:02:23 +02:00
2019-10-03 22:09:53 +02:00
if ( g_aEntityLagData [ i ] . iDeleted )
{
RemoveEdict ( g_aEntityLagData [ i ] . iEntity ) ;
}
}
2019-10-02 23:28:20 +02:00
}
2019-10-03 22:09:53 +02:00
public APLRes AskPluginLoad2 ( Handle myself , bool late , char [ ] error , int err_max )
2019-10-02 23:28:20 +02:00
{
2019-10-03 22:09:53 +02:00
g_bLateLoad = late ;
return APLRes_Success ;
2019-10-01 21:45:39 +02:00
}
2019-10-02 23:28:20 +02:00
public MRESReturn Detour_OnUTIL_Remove ( Handle hParams )
2019-10-01 21:45:39 +02:00
{
2019-10-05 15:17:10 +02:00
if ( g_bCleaningUp )
return MRES_Ignored ;
2019-10-02 23:28:20 +02:00
int entity = DHookGetParam ( hParams , 1 ) ;
2019-10-10 15:38:58 +02:00
if ( entity < 0 | | entity > MAX_EDICTS )
2019-10-02 23:28:20 +02:00
return MRES_Ignored ;
2019-10-03 12:02:23 +02:00
for ( int i = 0 ; i < g_iNumEntities ; i + + )
2019-10-02 23:28:20 +02:00
{
if ( g_aEntityLagData [ i ] . iEntity ! = entity )
continue ;
2019-10-20 12:44:34 +02:00
// let it die
if ( ! g_aEntityLagData [ i ] . bLateKill )
break ;
// ignore sleeping entities
if ( g_aEntityLagData [ i ] . iNotMoving > = MAX_RECORDS )
break ;
2019-10-02 23:28:20 +02:00
if ( ! g_aEntityLagData [ i ] . iDeleted )
2019-10-05 15:17:10 +02:00
{
2019-10-02 23:28:20 +02:00
g_aEntityLagData [ i ] . iDeleted = GetGameTickCount ( ) ;
2019-10-06 20:24:43 +02:00
PrintToBoth ( " [%d] !!!!!!!!!!! Detour_OnUTIL_Remove: %d / ent: %d " , GetGameTickCount ( ) , i , entity ) ;
2019-10-05 15:17:10 +02:00
}
2019-10-02 23:28:20 +02:00
return MRES_Supercede ;
}
return MRES_Ignored ;
2019-10-01 21:45:39 +02:00
}
2019-10-03 22:09:53 +02:00
public MRESReturn Detour_OnRestartRound ( )
{
2019-10-05 15:17:10 +02:00
g_bCleaningUp = true ;
2019-10-03 22:09:53 +02:00
PrintToBoth ( " Detour_OnRestartRound with %d entries. " , g_iNumEntities ) ;
for ( int i = 0 ; i < g_iNumEntities ; i + + )
{
2019-10-10 15:38:58 +02:00
g_aBlockTriggerTouch [ g_aEntityLagData [ i ] . iEntity ] = 0 ;
2019-10-05 15:17:10 +02:00
2019-10-20 12:44:34 +02:00
for ( int client = 1 ; client < = MaxClients ; client + + )
2019-10-05 15:17:10 +02:00
{
2019-10-20 12:44:34 +02:00
g_aaBlockTouch [ client ] [ g_aEntityLagData [ i ] . iEntity ] = 0 ;
}
2019-10-05 15:17:10 +02:00
2019-10-20 12:44:34 +02:00
if ( g_aEntityLagData [ i ] . iDeleted )
{
2019-10-05 15:17:10 +02:00
if ( IsValidEntity ( g_aEntityLagData [ i ] . iEntity ) )
RemoveEdict ( g_aEntityLagData [ i ] . iEntity ) ;
}
2019-10-06 20:24:43 +02:00
g_aEntityLagData [ i ] . iEntity = INVALID_ENT_REFERENCE ;
2019-10-03 22:09:53 +02:00
}
g_iNumEntities = 0 ;
2019-10-05 15:17:10 +02:00
g_bCleaningUp = false ;
2019-10-03 22:09:53 +02:00
return MRES_Ignored ;
}
2019-11-01 15:58:14 +01:00
// https://developer.valvesoftware.com/wiki/Logic_measure_movement
2019-11-02 01:35:20 +01:00
int g_OnSetTarget_pThis ;
public MRESReturn Detour_OnSetTargetPre ( int pThis , Handle hParams )
{
g_OnSetTarget_pThis = pThis ;
return MRES_Ignored ;
}
public MRESReturn Detour_OnSetTargetPost ( Handle hParams )
2019-11-01 15:58:14 +01:00
{
2019-11-02 01:35:20 +01:00
int entity = GetEntPropEnt ( g_OnSetTarget_pThis , Prop_Data , " m_hTarget " ) ;
2019-11-01 15:58:14 +01:00
if ( ! IsValidEntity ( entity ) )
return MRES_Ignored ;
char sClassname [ 64 ] ;
if ( ! GetEntityClassname ( entity , sClassname , sizeof ( sClassname ) ) )
return MRES_Ignored ;
2019-11-05 16:54:59 +01:00
if ( ! ( StrEqual ( sClassname , " trigger_hurt " , false ) | |
StrEqual ( sClassname , " trigger_push " , false ) | |
StrEqual ( sClassname , " trigger_teleport " , false ) ) )
{
2019-11-01 15:58:14 +01:00
return MRES_Ignored ;
2019-11-05 16:54:59 +01:00
}
2019-11-01 15:58:14 +01:00
if ( AddEntityForLagCompensation ( entity , true ) )
{
// Filter the trigger from being touched outside of the lag compensation
g_aBlockTriggerTouch [ entity ] = 1 ;
}
return MRES_Ignored ;
}
2019-10-01 21:45:39 +02:00
public void OnMapStart ( )
{
bool bLate = g_bLateLoad ;
g_bLateLoad = false ;
/* Late Load */
if ( bLate )
{
for ( int client = 1 ; client < = MaxClients ; client + + )
{
if ( IsClientInGame ( client ) )
OnClientPutInServer ( client ) ;
}
int entity = INVALID_ENT_REFERENCE ;
while ( ( entity = FindEntityByClassname ( entity , " * " ) ) ! = INVALID_ENT_REFERENCE )
{
char sClassname [ 64 ] ;
if ( GetEntityClassname ( entity , sClassname , sizeof ( sClassname ) ) )
OnEntitySpawned ( entity , sClassname ) ;
}
}
}
public void OnClientPutInServer ( int client )
{
}
public void OnRunThinkFunctions ( bool simulating )
{
2019-10-03 12:02:23 +02:00
for ( int i = 0 ; i < g_iNumEntities ; i + + )
2019-10-01 21:45:39 +02:00
{
if ( ! IsValidEntity ( g_aEntityLagData [ i ] . iEntity ) )
{
2019-10-06 20:24:43 +02:00
PrintToBoth ( " !!!!!!!!!!! OnRunThinkFunctions SHIT deleted: %d / %d " , i , g_aEntityLagData [ i ] . iEntity ) ;
2019-10-03 12:02:23 +02:00
RemoveRecord ( i ) ;
i - - ; continue ;
2019-10-01 21:45:39 +02:00
}
2019-10-08 00:00:19 +02:00
// Save old touchStamp
int touchStamp = GetEntProp ( g_aEntityLagData [ i ] . iEntity , Prop_Data , " touchStamp " ) ;
g_aEntityLagData [ i ] . iTouchStamp = touchStamp ;
// We have to increase the touchStamp by 1 here to avoid breaking the touchlink.
// The touchStamp is incremented by 1 every time an entities physics are simulated.
// When two entities touch then a touchlink is created on both entities with the touchStamp of either entity.
// Usually the player would touch the trigger first and then the trigger would touch the player later on in the same frame.
// The trigger touching the player would fix up the touchStamp (which was increased by 1 by the trigger physics simulate)
// But since we're blocking the trigger from ever touching a player outside of here we need to manually increase it by 1 up front
// so the correct +1'd touchStamp is stored in the touchlink.
// After simulating the players we restore the old touchStamp (-1) and when the entity is simulated it will increase it again by 1
// Thus both touchlinks will have the correct touchStamp value.
2019-10-20 12:44:34 +02:00
// The touchStamp doesn't increase when the entity is idle, however it also doesn't check untouch so we're fine.
2019-10-08 00:00:19 +02:00
touchStamp + + ;
SetEntProp ( g_aEntityLagData [ i ] . iEntity , Prop_Data , " touchStamp " , touchStamp ) ;
2019-10-02 23:28:20 +02:00
if ( g_aEntityLagData [ i ] . iDeleted )
{
2019-10-24 00:10:29 +02:00
if ( g_aEntityLagData [ i ] . iDeleted + MAX_RECORDS < = GetGameTickCount ( ) )
2019-10-02 23:28:20 +02:00
{
2019-10-03 22:09:53 +02:00
PrintToBoth ( " [%d] !!!!!!!!!!! RemoveEdict: %d / ent: %d " , GetGameTickCount ( ) , i , g_aEntityLagData [ i ] . iEntity ) ;
2019-10-03 12:02:23 +02:00
// calls OnEntityDestroyed right away
// which calls RemoveRecord
// which moves the next element to our current position
2019-10-02 23:28:20 +02:00
RemoveEdict ( g_aEntityLagData [ i ] . iEntity ) ;
2019-10-03 12:02:23 +02:00
i - - ; continue ;
2019-10-02 23:28:20 +02:00
}
continue ;
}
2019-10-05 15:17:10 +02:00
if ( g_aEntityLagData [ i ] . iNotMoving > = MAX_RECORDS )
continue ;
2019-10-01 21:45:39 +02:00
RecordDataIntoRecord ( g_aEntityLagData [ i ] . iEntity , g_aEntityLagData [ i ] . RestoreData ) ;
2019-10-03 10:39:27 +02:00
2019-10-02 14:19:09 +02:00
# if defined DEBUG
2019-10-02 18:54:19 +02:00
LogMessage ( " 1 [%d] [%d] index %d, RECORD entity %d " , GetGameTickCount ( ) , i , simulating , g_aEntityLagData [ i ] . iEntity , g_aEntityLagData [ i ] . iRecordIndex ) ;
2019-10-02 14:19:09 +02:00
LogMessage ( " %f %f %f " ,
g_aEntityLagData [ i ] . RestoreData . vecOrigin [ 0 ] ,
g_aEntityLagData [ i ] . RestoreData . vecOrigin [ 1 ] ,
g_aEntityLagData [ i ] . RestoreData . vecOrigin [ 2 ]
) ;
# endif
2019-10-01 21:45:39 +02:00
}
}
public Action OnPlayerRunCmd ( int client , int & buttons , int & impulse , float vel [ 3 ] , float angles [ 3 ] , int & weapon , int & subtype , int & cmdnum , int & tickcount , int & seed , int mouse [ 2 ] )
{
2019-10-02 12:22:35 +02:00
if ( ! IsPlayerAlive ( client ) )
return Plugin_Continue ;
2019-10-20 12:44:34 +02:00
int iGameTick = GetGameTickCount ( ) ;
int iDelta = iGameTick - tickcount ;
2019-10-10 15:38:58 +02:00
if ( iDelta < 0 )
iDelta = 0 ;
if ( iDelta > MAX_RECORDS )
iDelta = MAX_RECORDS ;
2019-11-03 01:11:40 +01:00
int iPlayerSimTick = iGameTick - iDelta ;
2019-10-01 21:45:39 +02:00
2019-10-03 12:02:23 +02:00
for ( int i = 0 ; i < g_iNumEntities ; i + + )
2019-10-01 21:45:39 +02:00
{
2019-10-20 12:44:34 +02:00
int iEntity = g_aEntityLagData [ i ] . iEntity ;
2019-10-01 21:45:39 +02:00
2019-10-10 15:38:58 +02:00
// Entity too new, the client couldn't even see it yet.
2019-10-24 00:10:29 +02:00
if ( g_aEntityLagData [ i ] . iSpawned > iPlayerSimTick )
2019-10-20 12:44:34 +02:00
{
g_aaBlockTouch [ client ] [ iEntity ] = 1 ;
continue ;
}
else if ( g_aEntityLagData [ i ] . iSpawned = = iPlayerSimTick )
{
g_aaBlockTouch [ client ] [ iEntity ] = 0 ;
}
2019-10-02 23:28:20 +02:00
if ( g_aEntityLagData [ i ] . iDeleted )
{
2019-10-24 00:10:29 +02:00
if ( g_aEntityLagData [ i ] . iDeleted < = iPlayerSimTick )
2019-10-05 15:17:10 +02:00
{
2019-10-20 12:44:34 +02:00
g_aaBlockTouch [ client ] [ iEntity ] = 1 ;
2019-10-02 23:28:20 +02:00
continue ;
2019-10-05 15:17:10 +02:00
}
2019-10-02 23:28:20 +02:00
}
2019-10-24 00:10:29 +02:00
if ( g_aEntityLagData [ i ] . iNotMoving > = MAX_RECORDS )
continue ;
2019-11-03 01:11:40 +01:00
// +1 because the newest record in the list is one tick old
// this is because we simulate players first
// hence no new entity record was inserted on the current tick
2019-11-03 17:18:43 +01:00
iDelta + = 1 ;
if ( iDelta > = g_aEntityLagData [ i ] . iNumRecords )
iDelta = g_aEntityLagData [ i ] . iNumRecords - 1 ;
int iRecordIndex = g_aEntityLagData [ i ] . iRecordIndex - iDelta ;
2019-10-01 21:45:39 +02:00
if ( iRecordIndex < 0 )
iRecordIndex + = MAX_RECORDS ;
2019-10-20 12:44:34 +02:00
RestoreEntityFromRecord ( iEntity , g_aaLagRecords [ i ] [ iRecordIndex ] ) ;
2019-11-05 22:26:38 +01:00
g_aEntityLagData [ i ] . bRestore | = ! g_aEntityLagData [ i ] . iDeleted ;
2019-10-01 21:45:39 +02:00
# if defined DEBUG
2019-10-20 12:44:34 +02:00
LogMessage ( " 2 [%d] index %d, Entity %d -> iDelta = %d | Record = %d " , iGameTick , i , iEntity , iDelta , iRecordIndex ) ;
2019-10-01 21:45:39 +02:00
LogMessage ( " %f %f %f " ,
g_aaLagRecords [ i ] [ iRecordIndex ] . vecOrigin [ 0 ] ,
g_aaLagRecords [ i ] [ iRecordIndex ] . vecOrigin [ 1 ] ,
g_aaLagRecords [ i ] [ iRecordIndex ] . vecOrigin [ 2 ]
) ;
# endif
}
return Plugin_Continue ;
}
2019-10-02 18:54:19 +02:00
public void OnPostPlayerThinkFunctions ( )
2019-10-01 21:45:39 +02:00
{
2019-10-03 12:02:23 +02:00
for ( int i = 0 ; i < g_iNumEntities ; i + + )
2019-10-01 21:45:39 +02:00
{
2019-10-08 00:00:19 +02:00
SetEntProp ( g_aEntityLagData [ i ] . iEntity , Prop_Data , " touchStamp " , g_aEntityLagData [ i ] . iTouchStamp ) ;
2019-10-03 10:39:27 +02:00
if ( ! g_aEntityLagData [ i ] . bRestore )
2019-10-01 21:45:39 +02:00
continue ;
2019-10-16 15:18:09 +02:00
RestoreEntityFromRecord ( g_aEntityLagData [ i ] . iEntity , g_aEntityLagData [ i ] . RestoreData ) ;
2019-10-01 21:45:39 +02:00
g_aEntityLagData [ i ] . bRestore = false ;
# if defined DEBUG
2019-10-02 18:54:19 +02:00
LogMessage ( " 3 [%d] index %d, RESTORE entity %d " , GetGameTickCount ( ) , i , g_aEntityLagData [ i ] . iEntity , g_aEntityLagData [ i ] . iRecordIndex ) ;
2019-10-01 21:45:39 +02:00
LogMessage ( " %f %f %f " ,
g_aEntityLagData [ i ] . RestoreData . vecOrigin [ 0 ] ,
g_aEntityLagData [ i ] . RestoreData . vecOrigin [ 1 ] ,
g_aEntityLagData [ i ] . RestoreData . vecOrigin [ 2 ]
) ;
# endif
}
2019-10-02 14:19:09 +02:00
2019-10-10 15:38:58 +02:00
FilterTriggerTouchPlayers ( g_aBlockTriggerTouch , true ) ;
2019-10-01 21:45:39 +02:00
}
public void OnRunThinkFunctionsPost ( bool simulating )
{
2019-10-03 12:02:23 +02:00
for ( int i = 0 ; i < g_iNumEntities ; i + + )
2019-10-01 21:45:39 +02:00
{
2019-10-02 23:28:20 +02:00
if ( g_aEntityLagData [ i ] . iDeleted )
2019-10-01 21:45:39 +02:00
{
2019-10-02 23:28:20 +02:00
if ( g_aEntityLagData [ i ] . iRecordsValid )
{
g_aEntityLagData [ i ] . iRecordIndex + + ;
if ( g_aEntityLagData [ i ] . iRecordIndex > = MAX_RECORDS )
g_aEntityLagData [ i ] . iRecordIndex = 0 ;
g_aEntityLagData [ i ] . iRecordsValid - - ;
}
2019-10-01 21:45:39 +02:00
continue ;
}
2019-10-03 12:02:23 +02:00
LagRecord TmpRecord ;
RecordDataIntoRecord ( g_aEntityLagData [ i ] . iEntity , TmpRecord ) ;
2019-10-03 22:09:53 +02:00
// sleep detection
2019-10-02 18:54:19 +02:00
{
2019-10-03 12:02:23 +02:00
int iOldRecord = g_aEntityLagData [ i ] . iRecordIndex ;
2019-10-02 18:54:19 +02:00
2019-11-05 22:26:38 +01:00
if ( CompareVectors ( g_aaLagRecords [ i ] [ iOldRecord ] . vecOrigin , TmpRecord . vecOrigin ) & &
CompareVectors ( g_aaLagRecords [ i ] [ iOldRecord ] . vecAngles , TmpRecord . vecAngles ) )
2019-10-03 12:02:23 +02:00
{
g_aEntityLagData [ i ] . iNotMoving + + ;
2019-10-20 12:44:34 +02:00
# if defined DEBUG
2019-10-03 12:02:23 +02:00
if ( g_aEntityLagData [ i ] . iNotMoving = = MAX_RECORDS )
2019-10-03 22:09:53 +02:00
{
char sClassname [ 64 ] ;
GetEntityClassname ( g_aEntityLagData [ i ] . iEntity , sClassname , sizeof ( sClassname ) ) ;
char sTargetname [ 64 ] ;
GetEntPropString ( g_aEntityLagData [ i ] . iEntity , Prop_Data , " m_iName " , sTargetname , sizeof ( sTargetname ) ) ;
int iHammerID = GetEntProp ( g_aEntityLagData [ i ] . iEntity , Prop_Data , " m_iHammerID " ) ;
PrintToBoth ( " [%d] entity %d (%s) \" %s \" (#%d) index %d GOING TO SLEEP " , GetGameTickCount ( ) , g_aEntityLagData [ i ] . iEntity , sClassname , sTargetname , iHammerID , i ) ;
}
2019-10-20 12:44:34 +02:00
# endif
2019-10-03 12:02:23 +02:00
}
else
{
2019-10-20 12:44:34 +02:00
# if defined DEBUG
2019-10-03 12:02:23 +02:00
if ( g_aEntityLagData [ i ] . iNotMoving > = MAX_RECORDS )
2019-10-03 22:09:53 +02:00
{
char sClassname [ 64 ] ;
GetEntityClassname ( g_aEntityLagData [ i ] . iEntity , sClassname , sizeof ( sClassname ) ) ;
char sTargetname [ 64 ] ;
GetEntPropString ( g_aEntityLagData [ i ] . iEntity , Prop_Data , " m_iName " , sTargetname , sizeof ( sTargetname ) ) ;
int iHammerID = GetEntProp ( g_aEntityLagData [ i ] . iEntity , Prop_Data , " m_iHammerID " ) ;
PrintToBoth ( " [%d] entity %d (%s) \" %s \" (#%d) index %d WAKING UP " , GetGameTickCount ( ) , g_aEntityLagData [ i ] . iEntity , sClassname , sTargetname , iHammerID , i ) ;
}
2019-10-20 12:44:34 +02:00
# endif
2019-10-03 12:02:23 +02:00
g_aEntityLagData [ i ] . iNotMoving = 0 ;
2019-10-02 18:54:19 +02:00
}
2019-10-03 12:02:23 +02:00
if ( g_aEntityLagData [ i ] . iNotMoving > = MAX_RECORDS )
continue ;
2019-10-02 18:54:19 +02:00
}
2019-10-01 21:45:39 +02:00
g_aEntityLagData [ i ] . iRecordIndex + + ;
if ( g_aEntityLagData [ i ] . iRecordIndex > = MAX_RECORDS )
g_aEntityLagData [ i ] . iRecordIndex = 0 ;
if ( g_aEntityLagData [ i ] . iNumRecords < MAX_RECORDS )
2019-10-02 23:28:20 +02:00
g_aEntityLagData [ i ] . iRecordsValid = + + g_aEntityLagData [ i ] . iNumRecords ;
2019-10-01 21:45:39 +02:00
2019-10-03 12:02:23 +02:00
LagRecord_Copy ( g_aaLagRecords [ i ] [ g_aEntityLagData [ i ] . iRecordIndex ] , TmpRecord ) ;
2019-10-01 21:45:39 +02:00
# if defined DEBUG
2019-10-02 18:54:19 +02:00
LogMessage ( " 4 [%d] index %d, RECORD entity %d into %d " , GetGameTickCount ( ) , i , g_aEntityLagData [ i ] . iEntity , g_aEntityLagData [ i ] . iRecordIndex ) ;
2019-10-01 21:45:39 +02:00
LogMessage ( " %f %f %f " ,
2019-10-03 12:02:23 +02:00
TmpRecord . vecOrigin [ 0 ] ,
TmpRecord . vecOrigin [ 1 ] ,
TmpRecord . vecOrigin [ 2 ]
2019-10-01 21:45:39 +02:00
) ;
# endif
}
2019-10-02 14:19:09 +02:00
2019-10-10 15:38:58 +02:00
FilterTriggerTouchPlayers ( g_aBlockTriggerTouch , false ) ;
2019-10-01 21:45:39 +02:00
}
void RecordDataIntoRecord ( int iEntity , LagRecord Record )
{
2019-10-02 23:28:20 +02:00
SDKCall ( g_hGetAbsOrigin , iEntity , Record . vecOrigin ) ;
2019-10-06 20:24:43 +02:00
GetEntPropVector ( iEntity , Prop_Data , " m_angRotation " , Record . vecAngles ) ;
Record . flSimulationTime = GetEntPropFloat ( iEntity , Prop_Data , " m_flSimulationTime " ) ;
2019-10-01 21:45:39 +02:00
}
2019-10-16 15:18:09 +02:00
void RestoreEntityFromRecord ( int iEntity , LagRecord Record )
2019-10-01 21:45:39 +02:00
{
2019-10-06 20:24:43 +02:00
SDKCall ( g_hSetLocalAngles , iEntity , Record . vecAngles ) ;
2019-10-02 18:54:19 +02:00
SDKCall ( g_hSetAbsOrigin , iEntity , Record . vecOrigin ) ;
2019-10-06 20:24:43 +02:00
SetEntPropFloat ( iEntity , Prop_Data , " m_flSimulationTime " , Record . flSimulationTime ) ;
2019-10-01 21:45:39 +02:00
}
2019-10-20 12:44:34 +02:00
bool AddEntityForLagCompensation ( int iEntity , bool bLateKill )
2019-10-01 21:45:39 +02:00
{
2019-10-05 15:17:10 +02:00
if ( g_bCleaningUp )
return false ;
2019-10-03 10:39:27 +02:00
if ( g_iNumEntities = = MAX_ENTITIES )
2019-10-16 15:18:09 +02:00
{
char sClassname [ 64 ] ;
GetEntityClassname ( iEntity , sClassname , sizeof ( sClassname ) ) ;
char sTargetname [ 64 ] ;
GetEntPropString ( iEntity , Prop_Data , " m_iName " , sTargetname , sizeof ( sTargetname ) ) ;
int iHammerID = GetEntProp ( iEntity , Prop_Data , " m_iHammerID " ) ;
PrintToBoth ( " [%d] OUT OF LAGCOMP SLOTS entity %d (%s) \" %s \" (#%d) " , GetGameTickCount ( ) , iEntity , sClassname , sTargetname , iHammerID ) ;
2019-10-03 10:39:27 +02:00
return false ;
2019-10-16 15:18:09 +02:00
}
2019-10-03 10:39:27 +02:00
2019-10-03 12:02:23 +02:00
for ( int i = 0 ; i < g_iNumEntities ; i + + )
2019-10-01 21:45:39 +02:00
{
if ( g_aEntityLagData [ i ] . iEntity = = iEntity )
return true ;
}
2019-10-03 12:02:23 +02:00
int i = g_iNumEntities ;
g_iNumEntities + + ;
2019-10-01 21:45:39 +02:00
2019-10-03 12:02:23 +02:00
g_aEntityLagData [ i ] . iEntity = iEntity ;
2019-10-03 22:09:53 +02:00
g_aEntityLagData [ i ] . iRecordIndex = 0 ;
g_aEntityLagData [ i ] . iNumRecords = 1 ;
g_aEntityLagData [ i ] . iRecordsValid = 1 ;
2019-10-10 15:38:58 +02:00
g_aEntityLagData [ i ] . iSpawned = GetGameTickCount ( ) ;
2019-10-03 12:02:23 +02:00
g_aEntityLagData [ i ] . iDeleted = 0 ;
g_aEntityLagData [ i ] . iNotMoving = MAX_RECORDS ;
g_aEntityLagData [ i ] . bRestore = false ;
2019-10-20 12:44:34 +02:00
g_aEntityLagData [ i ] . bLateKill = bLateKill ;
2019-10-02 23:28:20 +02:00
2019-10-16 15:18:09 +02:00
RecordDataIntoRecord ( iEntity , g_aaLagRecords [ i ] [ 0 ] ) ;
2019-10-03 22:09:53 +02:00
2019-10-03 12:02:23 +02:00
{
char sClassname [ 64 ] ;
2019-10-16 15:18:09 +02:00
GetEntityClassname ( iEntity , sClassname , sizeof ( sClassname ) ) ;
2019-10-02 23:28:20 +02:00
2019-10-03 12:02:23 +02:00
char sTargetname [ 64 ] ;
2019-10-16 15:18:09 +02:00
GetEntPropString ( iEntity , Prop_Data , " m_iName " , sTargetname , sizeof ( sTargetname ) ) ;
2019-10-02 23:28:20 +02:00
2019-10-16 15:18:09 +02:00
int iHammerID = GetEntProp ( iEntity , Prop_Data , " m_iHammerID " ) ;
2019-10-03 10:39:27 +02:00
2019-10-03 22:09:53 +02:00
PrintToBoth ( " [%d] added entity %d (%s) \" %s \" (#%d) under index %d " , GetGameTickCount ( ) , iEntity , sClassname , sTargetname , iHammerID , i ) ;
2019-10-01 21:45:39 +02:00
}
2019-10-03 10:39:27 +02:00
2019-10-03 12:02:23 +02:00
return true ;
2019-10-01 21:45:39 +02:00
}
public void OnEntitySpawned ( int entity , const char [ ] classname )
{
2019-10-05 15:17:10 +02:00
if ( g_bCleaningUp )
return ;
2019-10-10 15:38:58 +02:00
if ( entity < 0 | | entity > MAX_EDICTS )
2019-10-01 21:45:39 +02:00
return ;
2019-10-06 20:24:43 +02:00
if ( ! IsValidEntity ( entity ) )
return ;
2019-10-10 15:38:58 +02:00
2019-11-05 16:54:59 +01:00
bool bTrigger = StrEqual ( classname , " trigger_hurt " , false ) | |
StrEqual ( classname , " trigger_push " , false ) | |
StrEqual ( classname , " trigger_teleport " , false ) ;
2019-11-05 22:26:38 +01:00
bool bMoving = ! strncmp ( classname , " func_physbox " , 12 , false ) ;
2019-10-10 15:38:58 +02:00
2019-11-05 16:54:59 +01:00
if ( ! bTrigger & & ! bMoving )
2019-10-01 21:45:39 +02:00
return ;
2019-11-01 15:58:14 +01:00
// Don't lag compensate anything that could be parented to a player
// The player simulation would usually move the entity,
// but we would overwrite that position change by restoring the entity to its previous state.
int iParent = INVALID_ENT_REFERENCE ;
2019-10-01 21:45:39 +02:00
char sParentClassname [ 64 ] ;
2019-11-01 15:58:14 +01:00
for ( int iTmp = entity ; ; )
2019-10-02 18:54:19 +02:00
{
2019-11-01 15:58:14 +01:00
iTmp = GetEntPropEnt ( iTmp , Prop_Data , " m_pParent " ) ;
if ( iTmp = = INVALID_ENT_REFERENCE )
2019-10-02 18:54:19 +02:00
break ;
2019-10-01 21:45:39 +02:00
2019-11-01 15:58:14 +01:00
iParent = iTmp ;
2019-10-02 18:54:19 +02:00
GetEntityClassname ( iParent , sParentClassname , sizeof ( sParentClassname ) ) ;
2019-10-08 00:00:19 +02:00
2019-11-01 15:58:14 +01:00
if ( StrEqual ( sParentClassname , " player " ) | |
2019-11-01 19:51:18 +01:00
! strncmp ( sParentClassname , " weapon_ " , 7 ) )
2019-10-02 18:54:19 +02:00
{
2019-11-01 15:58:14 +01:00
return ;
2019-10-02 18:54:19 +02:00
}
}
2019-11-05 16:54:59 +01:00
// Lag compensate all moving stuff
if ( bMoving )
2019-11-01 15:58:14 +01:00
{
AddEntityForLagCompensation ( entity , false ) ;
2019-10-01 21:45:39 +02:00
return ;
2019-11-01 15:58:14 +01:00
}
2019-10-01 21:45:39 +02:00
2019-11-01 15:58:14 +01:00
// Lag compensate all (non player-) parented hurt triggers
2019-11-05 16:54:59 +01:00
if ( bTrigger & & iParent > MaxClients & & iParent < MAX_EDICTS )
2019-11-01 15:58:14 +01:00
{
if ( AddEntityForLagCompensation ( entity , true ) )
{
// Filter the trigger from being touched outside of the lag compensation
g_aBlockTriggerTouch [ entity ] = 1 ;
}
}
2019-10-01 21:45:39 +02:00
}
public void OnEntityDestroyed ( int entity )
{
2019-10-05 15:17:10 +02:00
if ( g_bCleaningUp )
2019-10-01 21:45:39 +02:00
return ;
2019-10-10 15:38:58 +02:00
if ( entity < 0 | | entity > MAX_EDICTS )
2019-10-05 15:17:10 +02:00
return ;
2019-10-01 21:45:39 +02:00
2019-10-06 20:24:43 +02:00
if ( ! IsValidEntity ( entity ) )
return ;
2019-10-03 12:02:23 +02:00
for ( int i = 0 ; i < g_iNumEntities ; i + + )
2019-10-01 21:45:39 +02:00
{
2019-10-03 10:39:27 +02:00
if ( g_aEntityLagData [ i ] . iEntity ! = entity )
continue ;
2019-10-03 12:02:23 +02:00
RemoveRecord ( i ) ;
2019-10-03 10:39:27 +02:00
return ;
2019-10-01 21:45:39 +02:00
}
}
2019-10-03 12:02:23 +02:00
void RemoveRecord ( int index )
{
2019-10-05 15:17:10 +02:00
if ( g_bCleaningUp )
return ;
2019-10-03 22:09:53 +02:00
{
char sClassname [ 64 ] ;
GetEntityClassname ( g_aEntityLagData [ index ] . iEntity , sClassname , sizeof ( sClassname ) ) ;
char sTargetname [ 64 ] ;
GetEntPropString ( g_aEntityLagData [ index ] . iEntity , Prop_Data , " m_iName " , sTargetname , sizeof ( sTargetname ) ) ;
int iHammerID = GetEntProp ( g_aEntityLagData [ index ] . iEntity , Prop_Data , " m_iHammerID " ) ;
PrintToBoth ( " [%d] RemoveRecord %d / %d (%s) \" %s \" (#%d), num: %d " , GetGameTickCount ( ) , index , g_aEntityLagData [ index ] . iEntity , sClassname , sTargetname , iHammerID , g_iNumEntities ) ;
}
2019-10-10 15:38:58 +02:00
g_aBlockTriggerTouch [ g_aEntityLagData [ index ] . iEntity ] = 0 ;
2019-10-05 15:17:10 +02:00
2019-10-20 12:44:34 +02:00
for ( int client = 1 ; client < = MaxClients ; client + + )
2019-10-05 15:17:10 +02:00
{
2019-10-20 12:44:34 +02:00
g_aaBlockTouch [ client ] [ g_aEntityLagData [ index ] . iEntity ] = 0 ;
2019-10-05 15:17:10 +02:00
}
2019-10-06 20:24:43 +02:00
g_aEntityLagData [ index ] . iEntity = INVALID_ENT_REFERENCE ;
2019-10-03 12:02:23 +02:00
for ( int src = index + 1 ; src < g_iNumEntities ; src + + )
{
int dest = src - 1 ;
EntityLagData_Copy ( g_aEntityLagData [ dest ] , g_aEntityLagData [ src ] ) ;
2019-10-06 20:24:43 +02:00
g_aEntityLagData [ src ] . iEntity = INVALID_ENT_REFERENCE ;
2019-10-03 12:02:23 +02:00
int iNumRecords = g_aEntityLagData [ dest ] . iNumRecords ;
for ( int i = 0 ; i < iNumRecords ; i + + )
{
LagRecord_Copy ( g_aaLagRecords [ dest ] [ i ] , g_aaLagRecords [ src ] [ i ] ) ;
}
}
g_iNumEntities - - ;
}
void EntityLagData_Copy ( EntityLagData obj , const EntityLagData other )
{
obj . iEntity = other . iEntity ;
obj . iRecordIndex = other . iRecordIndex ;
obj . iNumRecords = other . iNumRecords ;
obj . iRecordsValid = other . iRecordsValid ;
2019-10-10 15:38:58 +02:00
obj . iSpawned = other . iSpawned ;
2019-10-03 12:02:23 +02:00
obj . iDeleted = other . iDeleted ;
obj . iNotMoving = other . iNotMoving ;
2019-10-08 00:00:19 +02:00
obj . iTouchStamp = other . iTouchStamp ;
2019-10-03 12:02:23 +02:00
obj . bRestore = other . bRestore ;
2019-10-20 12:44:34 +02:00
obj . bLateKill = other . bLateKill ;
2019-10-03 12:02:23 +02:00
LagRecord_Copy ( obj . RestoreData , other . RestoreData ) ;
}
void LagRecord_Copy ( LagRecord obj , const LagRecord other )
{
obj . vecOrigin [ 0 ] = other . vecOrigin [ 0 ] ;
obj . vecOrigin [ 1 ] = other . vecOrigin [ 1 ] ;
obj . vecOrigin [ 2 ] = other . vecOrigin [ 2 ] ;
obj . vecAngles [ 0 ] = other . vecAngles [ 0 ] ;
obj . vecAngles [ 1 ] = other . vecAngles [ 1 ] ;
obj . vecAngles [ 2 ] = other . vecAngles [ 2 ] ;
2019-10-06 20:24:43 +02:00
obj . flSimulationTime = other . flSimulationTime ;
2019-10-03 12:02:23 +02:00
}
2019-10-03 22:09:53 +02:00
2019-11-05 22:26:38 +01:00
bool CompareVectors ( const float vec1 [ 3 ] , const float vec2 [ 3 ] )
{
return vec1 [ 0 ] = = vec2 [ 0 ] & & vec1 [ 1 ] = = vec2 [ 1 ] & & vec1 [ 2 ] = = vec2 [ 2 ] ;
}
2019-10-03 22:09:53 +02:00
stock void PrintToBoth ( const char [ ] format , any . . . )
{
char buffer [ 254 ] ;
VFormat ( buffer , sizeof ( buffer ) , format , 2 ) ;
LogMessage ( buffer ) ;
for ( int i = 1 ; i < = MaxClients ; i + + )
{
if ( IsClientInGame ( i ) )
{
PrintToConsole ( i , " %s " , buffer ) ;
}
}
2019-11-05 22:26:38 +01:00
}