#include "includes.h"
// credits: https://yougame.biz/threads/325142/
// https://github.com/LAITHCOOL/laith-legendware-rework/blob/05d18bde61644291e29a5d81f9772c16770fe223/cheats/lagcompensation/animation_system.cpp
void handle_silent_updates(Player* player, c_animstate* state) {
if (!player || !state) return;
// Check if the last frame was updated or if there is a significant time difference
if (state->last_update_frame == g_csgo.m_globals->m_framecount) {
// No need to update, as the frame was recently updated
return;
}
// If a silent update is detected, reapply or interpolate the last known correct state
float time_difference = g_csgo.m_globals->m_curtime - state->last_update_time;
// If the time difference exceeds a threshold, consider it a silent update
if (time_difference > g_csgo.m_globals->m_interval * 2) {
// Reapply the last known state or interpolate to the current state
state->eye_yaw = interpolate(state->eye_yaw, player->m_flLowerBodyYawTarget(), time_difference);
}
// Update the state variables to the current frame and time
state->last_update_frame = g_csgo.m_globals->m_framecount;
state->last_update_time = g_csgo.m_globals->m_curtime;
// Mini update function for handling animations
void anims::mini_update(Player* player) {
if (!player) return; // check for null pointer.
C_AnimationLayer backup_layers[13];
player->GetAnimLayers(backup_layers);
// backup globalvars.
float curtime = g_csgo.m_globals->m_curtime;
float frame = g_csgo.m_globals->m_frame;
float frametime = g_csgo.m_globals->m_frametime;
// set frametime to IPT just like on the server during simulation.
g_csgo.m_globals->m_curtime = player->m_flSimulationTime();
g_csgo.m_globals->m_frame = g_csgo.m_cl->m_server_tick;
g_csgo.m_globals->m_frametime = g_csgo.m_globals->m_interval;
// remove abs velocity.
player->m_iEFlags() &= ~0x1000;
player->SetAbsVelocity(player->m_vecVelocity());
// Handle silent updates before updating animations
handle_silent_updates(player, player->m_AnimState());
// update animations.
g_hooks.m_UpdateClientSideAnimation(player);
// restore layers to networked.
player->SetAnimLayers(backup_layers);
// restore once we're done.
g_csgo.m_globals->m_frame = frame;
g_csgo.m_globals->m_curtime = curtime;
g_csgo.m_globals->m_frametime = frametime;
}
// function to reset state variables.
void reset_state_vars(Player* player, c_animstate* state, LagRecord* record) {
if (!player || !state || !record) return;
state->on_ground = !!(player->m_fFlags() & FL_ONGROUND);
state->landing = false;
state->abs_yaw_last = state->abs_yaw = player->m_flLowerBodyYawTarget();
state->eye_yaw = player->m_flLowerBodyYawTarget();
state->eye_pitch = record->m_eye_angles.x;
state->primary_cycle = record->m_layers[6].m_cycle;
state->move_weight = record->m_layers[6].m_weight;
state->strafe_sequence = record->m_layers[7].m_sequence;
state->strafe_change_weight = record->m_layers[7].m_weight;
state->strafe_change_cycle = record->m_layers[7].m_cycle;
state->acceleration_weight = record->m_layers[12].m_weight;
player->m_flOldSimulationTime() = state->last_update_time = record->m_sim_time - g_csgo.m_globals->m_interval;
state->last_update_frame = g_csgo.m_cl->m_server_tick - 1;
state->duration_in_air = 0.f;
record->m_anim_velocity = player->m_vecVelocity();
if (state->on_ground) {
record->m_anim_velocity.z = 0.f;
if (state->primary_cycle == 0.f || state->move_weight == 0.f) {
record->m_anim_velocity = vec3_t();
}
}
}
// function to apply previous animation state.
void apply_previous_state(Player* player, c_animstate* state, LagRecord* record, LagRecord* previous) {
if (!player || !state || !record || !previous) return;
player->SetAnimLayers(previous->m_layers);
player->SetPoseParameters(previous->m_poses);
state->primary_cycle = previous->m_layers[6].m_cycle;
state->move_weight = previous->m_layers[6].m_weight;
state->strafe_sequence = previous->m_layers[7].m_sequence;
state->strafe_change_weight = previous->m_layers[7].m_weight;
state->strafe_change_cycle = previous->m_layers[7].m_cycle;
state->acceleration_weight = previous->m_layers[12].m_weight;
// https://gitlab.com/KittenPopo/csgo-2018-source/-/blob/main/game/client/c_baseplayer.cpp#L1139
// void C_BasePlayer::PostDataUpdate( DataUpdateType_t updateType )
record->m_anim_velocity = (record->m_origin - previous->m_origin) * (1.f / game::TICKS_TO_TIME(record->m_lag));
// task for reader: add proper velocity recalculation
// hint CCSGOPlayerAnimState::SetUpAliveLoop
// Ensure the player is on the ground and adjust the z-component of the velocity
if ((previous->m_flags & FL_ONGROUND) && (record->m_flags & FL_ONGROUND)) {
record->m_anim_velocity.z = 0.f;
// Check if the player is in a fake walk state where the animation might have stopped
if (record->m_layers[6].m_playback_rate == 0.f) {
record->m_anim_velocity = vec3_t(); // Set velocity to zero if not moving
}
}
else {
// Handle airborne or jumping states where gravity and falling velocity should be calculated
if (!(previous->m_flags & FL_ONGROUND)) {
record->m_anim_velocity.z -= g_csgo.sv_gravity->GetFloat() * g_csgo.m_globals->m_interval;
}
}
// Apply the recalculated velocity to the player's current state
player->m_vecVelocity() = record->m_anim_velocity;
player->SetAbsVelocity(record->m_anim_velocity);
}
void SimulatePlayerActivity(Player* pPlayer, LagRecord* m_LagRecord, LagRecord* m_PrevRecord) {
if (!pPlayer || !m_LagRecord || !m_PrevRecord) return;
// get animation layers.
const C_AnimationLayer* m_JumpingLayer = &pPlayer->m_AnimOverlay()[ANIMATION_LAYER_MOVEMENT_JUMP_OR_FALL];
const C_AnimationLayer* m_LandingLayer = &pPlayer->m_AnimOverlay()[ANIMATION_LAYER_MOVEMENT_LAND_OR_CLIMB];
const C_AnimationLayer* m_PrevJumpingLayer = &pPlayer->m_AnimOverlay()[ANIMATION_LAYER_MOVEMENT_JUMP_OR_FALL];
const C_AnimationLayer* m_PrevLandingLayer = &pPlayer->m_AnimOverlay()[ANIMATION_LAYER_MOVEMENT_LAND_OR_CLIMB];
// detect jump/land, collect its data, rebuild time in air.
const int nJumpingActivity = pPlayer->GetSequenceActivity(m_JumpingLayer->m_sequence);
const int nLandingActivity = pPlayer->GetSequenceActivity(m_LandingLayer->m_sequence);
// collect jump data.
if (nJumpingActivity == ACT_CSGO_JUMP) {
// check duration bounds.
if (m_JumpingLayer->m_weight > 0.0f && m_JumpingLayer->m_playback_rate > 0.0f) {
// check cycle changed.
if (m_JumpingLayer->m_cycle < m_PrevJumpingLayer->m_cycle) {
m_LagRecord->m_flDurationInAir = m_JumpingLayer->m_cycle / m_JumpingLayer->m_playback_rate;
if (m_LagRecord->m_flDurationInAir > 0.0f) {
m_LagRecord->m_nActivityTick = game::TIME_TO_TICKS(m_LagRecord->m_sim_time - m_LagRecord->m_flDurationInAir) + 1;
m_LagRecord->m_nActivityType = EPlayerActivityC::CJump;
}
}
}
}
// collect land data.
if (nLandingActivity == ACT_CSGO_LAND_LIGHT || nLandingActivity == ACT_CSGO_LAND_HEAVY) {
// weight changing everytime on activity switch in this layer.
if (m_LandingLayer->m_weight > 0.0f && m_PrevLandingLayer->m_weight <= 0.0f) {
// check cycle changed.
if (m_LandingLayer->m_cycle > m_PrevLandingLayer->m_cycle) {
float flLandDuration = m_LandingLayer->m_cycle / m_LandingLayer->m_playback_rate;
if (flLandDuration > 0.0f) {
m_LagRecord->m_nActivityTick = game::TIME_TO_TICKS(m_LagRecord->m_sim_time - flLandDuration) + 1;
m_LagRecord->m_nActivityType = EPlayerActivityC::CLand;
// determine duration in air.
float flDurationInAir = (m_JumpingLayer->m_cycle - m_LandingLayer->m_cycle);
if (flDurationInAir < 0.0f)
flDurationInAir += 1.0f;
// set time in air.
m_LagRecord->m_flDurationInAir = flDurationInAir / m_JumpingLayer->m_playback_rate;
}
}
}
}
}
float ComputeActivityPlayback(Player* pPlayer, LagRecord* m_Record) {
if (!pPlayer || !m_Record) return 0.0f;
// get animation layers.
C_AnimationLayer* m_JumpingLayer = &pPlayer->m_AnimOverlay()[ANIMATION_LAYER_MOVEMENT_JUMP_OR_FALL];
C_AnimationLayer* m_LandingLayer = &pPlayer->m_AnimOverlay()[ANIMATION_LAYER_MOVEMENT_LAND_OR_CLIMB];
C_AnimationLayer* moving = &pPlayer->m_AnimOverlay()[ANIMATION_LAYER_MOVEMENT_MOVE];
// determine playback.
float flActivityPlayback = 0.0f;
switch (m_Record->m_nActivityType) {
case EPlayerActivityC::CJump:
{
flActivityPlayback = pPlayer->GetLayerSequenceCycleRate(m_JumpingLayer, m_JumpingLayer->m_sequence);
}
break;
case EPlayerActivityC::CLand:
{
flActivityPlayback = pPlayer->GetLayerSequenceCycleRate(m_LandingLayer, m_LandingLayer->m_sequence);
}
break;
}
return flActivityPlayback;
}
// Function to recalculate player velocity based on different conditions.
void RecalculateVelocity(Player* player, LagRecord* record, LagRecord* previous) {
// Check if the player is valid
if (!player || !record) return;
// Check if previous record exists
if (!previous) {
// Default velocity calculation, using current velocity and potentially other factors.
record->m_anim_velocity = player->m_vecVelocity();
return;
}
// Calculate the velocity difference between the current and previous records
vec3_t velocity_diff = record->m_origin - previous->m_origin;
float time_diff = game::TICKS_TO_TIME(record->m_lag);
// Handle case where there is no time difference to prevent division by zero
if (time_diff > 0.f) {
record->m_anim_velocity = velocity_diff * (1.f / time_diff);
}
else {
// Set velocity to zero if time difference is invalid
record->m_anim_velocity = vec3_t();
}
// Adjust z component for on-ground state
if (record->m_flags & FL_ONGROUND) {
record->m_anim_velocity.z = 0.f;
}
else {
// Apply gravity for airborne state
record->m_anim_velocity.z -= g_csgo.sv_gravity->GetFloat() * g_csgo.m_globals->m_interval;
}
}
// function to handle the main update logic.
void perform_update_logic(Player* player, LagRecord* record, LagRecord* previous, c_animstate* state) {
if (!player || !record || !state) return;
// simulate player activity (jump/land) before processing.
SimulatePlayerActivity(player, record, previous);
// Recalculate velocity before updating animations.
RecalculateVelocity(player, record, previous);
// compute activity playback ( jump and land ).
record->m_flActivityPlayback = ComputeActivityPlayback(player, record);
for (int i = 1; i <= record->m_lag; i++) {
const float interp = std::clamp(static_cast<float>(i) / static_cast<float>(record->m_lag), 0.f, 1.f);
if (previous && record->m_lag > 1) {
g_csgo.m_globals->m_curtime = math::lerp(interp, previous->m_sim_time, record->m_sim_time);
player->m_vecVelocity() = math::lerp(interp, previous->m_anim_velocity, record->m_anim_velocity);
player->m_flDuckAmount() = math::lerp(interp, previous->m_duck, record->m_duck);
// handle jump and land activity.
if (record->m_nActivityType != EPlayerActivityC::CNoActivity) {
if (record->m_lag == record->m_nActivityTick) {
// compute the correct animation layer.
int nLayer = ANIMATION_LAYER_MOVEMENT_JUMP_OR_FALL;
if (record->m_nActivityType == EPlayerActivityC::CLand)
nLayer = ANIMATION_LAYER_MOVEMENT_LAND_OR_CLIMB;
// set the player's animation state based on activity.
player->m_AnimOverlay()[nLayer].m_cycle = 0.0f;
player->m_AnimOverlay()[nLayer].m_weight = 0.0f;
player->m_AnimOverlay()[nLayer].m_playback_rate = record->m_flActivityPlayback;
// update player's ground state based on activity.
if (record->m_nActivityType == EPlayerActivityC::CJump)
player->m_fFlags() &= ~FL_ONGROUND;
else if (record->m_nActivityType == EPlayerActivityC::CLand)
player->m_fFlags() |= FL_ONGROUND;
}
else if (record->m_lag < record->m_nActivityTick) {
// force the player's ground state before the activity tick.
if (record->m_nActivityType == EPlayerActivityC::CJump)
player->m_fFlags() |= FL_ONGROUND;
else if (record->m_nActivityType == EPlayerActivityC::CLand)
player->m_fFlags() &= ~FL_ONGROUND;
}
}
}
else {
player->m_vecVelocity() = record->m_anim_velocity;
}
player->m_iEFlags() &= ~0x1000;
player->SetAbsVelocity(player->m_vecVelocity());
player->m_angEyeAngles() = record->m_eye_angles;
g_hooks.m_UpdateClientSideAnimation(player);
record->m_abs_ang = ang_t(0.f, state->abs_yaw, 0.f);
player->GetPoseParameters(record->m_poses);
}
player->UpdateCollisionBounds();
record->m_maxs = player->m_vecMaxs();
record->m_mins = player->m_vecMins();
player->SetAnimLayers(record->m_layers);
record->m_setup = player->SetupBones(record->m_bones, 128, BONE_USED_BY_ANYTHING, state->last_update_time);
}
// restore global variables after animation update.
void restore_globals(float frame, float curtime, float frametime) {
g_csgo.m_globals->m_frame = frame;
g_csgo.m_globals->m_curtime = curtime;
g_csgo.m_globals->m_frametime = frametime;
}
// update function for handling detailed player animations.
void anims::update(Player* player, LagRecord* record, LagRecord* previous) {
c_animstate* state = player->m_PlayerAnimState();
// backup globalvars.
float curtime = g_csgo.m_globals->m_curtime;
float frame = g_csgo.m_globals->m_frame;
float frametime = g_csgo.m_globals->m_frametime;
// backup current animation state.
AnimationBackup_t backup;
backup.store(player);
// set timing and simulate player animation state.
g_csgo.m_globals->m_curtime = record->m_anim_time;
g_csgo.m_globals->m_frame = g_csgo.m_cl->m_server_tick;
g_csgo.m_globals->m_frametime = g_csgo.m_globals->m_interval;
// handle player origins and reset state vars if needed.
player->SetAbsOrigin(player->m_vecOrigin());
// fix our animstate vars when animstate resets
if (state->last_update_time == 0.f || !previous) {
reset_state_vars(player, state, record);
}
// or fix animstate netvars using networked values
else if (previous) {
apply_previous_state(player, state, record, previous);
}
// resolve angles and update animations.
if (!previous || (record->m_lag != 1 || previous->m_lag != 1)) {
g_resolver.ResolveAngles(player, record);
}
perform_update_logic(player, record, previous, state);
// restore backup and globalvars.
backup.apply(player);
restore_globals(frame, curtime, frametime);
}
// task: hook C_BasePlayer::PostDataUpdate() and call this in it
// or use FRAME_NET_UPDATE_POSTDATAUPDATE_START
void anims::player_instance(Player* player) {
if (!player) return;
c_animstate* state = player->m_PlayerAnimState();
if (!state)
return;
AimPlayer* data = &g_aimbot.m_players[player->index() - 1];
if (!player->alive() || !g_cl.m_processing) {
if (player->m_flSimulationTime() != player->m_flOldSimulationTime())
mini_update(player);
data->m_records.clear();
data->m_clear_next = true;
return;
}
if (player->dormant()) {
if (data->m_records.empty() || !data->m_records[0]->dormant()) {
data->m_records.emplace_front(std::make_shared< LagRecord >(player));
LagRecord* current = data->m_records.front().get();
current->m_dormant = true;
current->m_setup = false;
}
data->m_clear_next = true;
return;
}
const float new_spawn = player->m_flSpawnTime();
// fix
if (player != data->m_player || data->m_clear_next || data->m_spawn != new_spawn) {
// reset animation state and records (they're all invalid).
game::ResetAnimationState(state);
data->m_records.clear();
data->m_walk_record = LagRecordMove{};
// alternative fix.
player->m_flOldSimulationTime() = player->m_flSimulationTime() - g_csgo.m_globals->m_interval;
}
// update our vars
data->m_player = player;
data->m_clear_next = false;
data->m_spawn = new_spawn;
// player updated.
if (data->m_records.empty() || data->m_records[0]->m_sim_time != player->m_flSimulationTime()) {
// emplace new record.
data->m_records.emplace_front(std::make_shared< LagRecord >(player));
// get our new record.
LagRecord* current = data->m_records.front().get();
current->m_dormant = false;
// update animations.
update(player, current, data->m_records.size() > 1 ? data->m_records[1].get() : nullptr);
}
// don't store records that are too old.
while (data->m_records.size() > 1) {
if (std::abs(g_csgo.m_cl->m_server_tick - data->m_records.back()->m_tick) < 256)
break;
data->m_records.pop_back();
}
}