Add deterministic seed trigger (real random_seed spread simulation)

This commit is contained in:
2026-08-05 17:19:02 +02:00
parent 67ab4b2c3c
commit d249ce94f0
6 changed files with 392 additions and 5 deletions
@@ -1,7 +1,9 @@
#include "CMisc.hpp" #include "CMisc.hpp"
#include <algorithm> #include <algorithm>
#include <cmath>
#include <cstdio> #include <cstdio>
#include <cstring>
#include <ctime> #include <ctime>
#include <mmsystem.h> #include <mmsystem.h>
@@ -30,6 +32,298 @@
#include <ImGui/imgui.h> #include <ImGui/imgui.h>
namespace
{
// Deterministic Valve RNG (matches the game's math::random_seed generator).
class CValveRng
{
public:
auto Seed( int s ) -> void
{
m_State = -std::abs( s );
m_Index = 0;
m_bSeeded = false;
}
auto Generate() -> int
{
if ( !m_bSeeded )
{
auto v = -m_State;
if ( v < 1 )
v = 1;
for ( int j = 39; j >= 0; --j )
{
v = LCG( v );
if ( j < 32 )
m_Table[ j ] = v;
}
m_State = v;
m_Index = m_Table[ 0 ];
m_bSeeded = true;
}
m_State = LCG( m_State );
const auto Index = m_Index / 0x4000000;
m_Index = m_Table[ Index ];
m_Table[ Index ] = m_State;
return m_Index;
}
auto RandomFloat( float Min = 0.f , float Max = 1.f ) -> float
{
const auto Raw = Generate();
const auto Norm = std::fminf( 0.99999988f , static_cast<float>( Raw ) * 4.6566129e-10f );
return Min + Norm * ( Max - Min );
}
private:
static auto LCG( int State ) -> int
{
const auto k = State / 127773;
auto Result = 16807 * ( State - k * 127773 ) - 2836 * k;
if ( Result < 0 )
Result += 2147483647;
return Result;
}
int m_State = 0;
int m_Index = 0;
int m_Table[ 32 ]{};
bool m_bSeeded = false;
};
// Minimal SHA-1 (for computing the spread seed from angles + tick).
class CSha1
{
public:
auto Reset() -> void
{
m_State[ 0 ] = 0x67452301;
m_State[ 1 ] = 0xEFCDAB89;
m_State[ 2 ] = 0x98BADCFE;
m_State[ 3 ] = 0x10325476;
m_State[ 4 ] = 0xC3D2E1F0;
m_Count = 0;
}
auto Update( const void* pData , std::size_t Len ) -> void
{
const auto* pBytes = static_cast<const uint8_t*>( pData );
auto Index = static_cast<std::size_t>( m_Count & 63 );
m_Count += Len;
std::size_t i = 0;
if ( Index )
{
auto PartLen = 64 - Index;
if ( Len >= PartLen )
{
std::memcpy( m_Buffer + Index , pBytes , PartLen );
Transform( m_Buffer );
i = PartLen;
}
else
{
std::memcpy( m_Buffer + Index , pBytes , Len );
return;
}
}
for ( ; i + 64 <= Len; i += 64 )
Transform( pBytes + i );
if ( i < Len )
std::memcpy( m_Buffer , pBytes + i , Len - i );
}
auto Final() -> void
{
uint8_t Padding[ 64 ]{};
Padding[ 0 ] = 0x80;
const auto Index = static_cast<std::size_t>( m_Count & 63 );
const auto PadLen = ( Index < 56 ) ? ( 56 - Index ) : ( 120 - Index );
const auto BitCount = m_Count * 8;
Update( Padding , PadLen );
uint8_t Bits[ 8 ]{};
for ( int i = 0; i < 8; ++i )
Bits[ 7 - i ] = static_cast<uint8_t>( BitCount >> ( i * 8 ) );
Update( Bits , 8 );
for ( int i = 0; i < 5; ++i )
{
m_Digest[ i * 4 + 0 ] = static_cast<uint8_t>( m_State[ i ] >> 24 );
m_Digest[ i * 4 + 1 ] = static_cast<uint8_t>( m_State[ i ] >> 16 );
m_Digest[ i * 4 + 2 ] = static_cast<uint8_t>( m_State[ i ] >> 8 );
m_Digest[ i * 4 + 3 ] = static_cast<uint8_t>( m_State[ i ] );
}
}
auto GetFirstUInt32() const -> uint32_t
{
uint32_t Result;
std::memcpy( &Result , m_Digest , sizeof Result );
return Result;
}
private:
static auto RotL( uint32_t v , int n ) -> uint32_t
{
return ( v << n ) | ( v >> ( 32 - n ) );
}
auto Transform( const uint8_t* pBlock ) -> void
{
uint32_t w[ 80 ]{};
for ( int i = 0; i < 16; ++i )
w[ i ] = static_cast<uint32_t>( pBlock[ i * 4 ] ) << 24 | static_cast<uint32_t>( pBlock[ i * 4 + 1 ] ) << 16 | static_cast<uint32_t>( pBlock[ i * 4 + 2 ] ) << 8 | static_cast<uint32_t>( pBlock[ i * 4 + 3 ] );
for ( int i = 16; i < 80; ++i )
w[ i ] = RotL( w[ i - 3 ] ^ w[ i - 8 ] ^ w[ i - 14 ] ^ w[ i - 16 ] , 1 );
auto a = m_State[ 0 ];
auto b = m_State[ 1 ];
auto c = m_State[ 2 ];
auto d = m_State[ 3 ];
auto e = m_State[ 4 ];
for ( int i = 0; i < 80; ++i )
{
uint32_t f , k;
if ( i < 20 )
{
f = ( b & c ) | ( ( ~b ) & d );
k = 0x5A827999;
}
else if ( i < 40 )
{
f = b ^ c ^ d;
k = 0x6ED9EBA1;
}
else if ( i < 60 )
{
f = ( b & c ) | ( b & d ) | ( c & d );
k = 0x8F1BBCDC;
}
else
{
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
const auto Temp = RotL( a , 5 ) + f + e + k + w[ i ];
e = d;
d = c;
c = RotL( b , 30 );
b = a;
a = Temp;
}
m_State[ 0 ] += a;
m_State[ 1 ] += b;
m_State[ 2 ] += c;
m_State[ 3 ] += d;
m_State[ 4 ] += e;
}
uint32_t m_State[ 5 ]{};
std::uint64_t m_Count = 0;
uint8_t m_Buffer[ 64 ]{};
uint8_t m_Digest[ 20 ]{};
};
auto NormalizeAngle( float Angle ) -> float
{
return Angle - std::floorf( Angle * 0.0027777778f + 0.5f ) * 360.0f;
}
auto QuantizeAngle( float Angle ) -> float
{
return std::floorf( NormalizeAngle( Angle ) * 2.0f ) * 0.5f;
}
auto GetSpreadSeed( const QAngle& ViewAngles , int Tick ) -> uint32_t
{
struct
{
float Pitch;
float Yaw;
int PlayerRenderTick;
} Buffer{};
Buffer.Pitch = QuantizeAngle( ViewAngles.m_x );
Buffer.Yaw = QuantizeAngle( ViewAngles.m_y );
Buffer.PlayerRenderTick = Tick;
CSha1 Hash;
Hash.Reset();
Hash.Update( &Buffer , 12 );
Hash.Final();
return Hash.GetFirstUInt32();
}
struct SeedSpread2_t
{
float x = 0.f;
float y = 0.f;
};
auto CalculateSeedSpread( int Seed , float Inaccuracy , float Spread , int ItemDefIdx ) -> SeedSpread2_t
{
constexpr auto k_RevolverId = 64;
constexpr auto k_NegevId = 28;
constexpr auto k_TwoPi = 6.2831853f;
CValveRng Rng;
Rng.Seed( Seed );
auto InacR = Rng.RandomFloat( 0.f , 1.f );
const auto InacA = Rng.RandomFloat( 0.f , k_TwoPi );
if ( ItemDefIdx == k_RevolverId )
InacR = 1.f - ( InacR * InacR );
else if ( ItemDefIdx == k_NegevId )
InacR = 1.f - ( InacR * InacR );
InacR *= Inaccuracy;
auto SprR = Rng.RandomFloat( 0.f , 1.f );
const auto SprA = Rng.RandomFloat( 0.f , k_TwoPi );
if ( ItemDefIdx == k_RevolverId )
SprR = 1.f - ( SprR * SprR );
else if ( ItemDefIdx == k_NegevId )
SprR = 1.f - ( SprR * SprR );
SprR *= Spread;
return
{
std::cosf( SprA ) * SprR + std::cosf( InacA ) * InacR,
std::sinf( SprA ) * SprR + std::sinf( InacA ) * InacR
};
}
}
static CMisc g_CMisc{}; static CMisc g_CMisc{};
auto CMisc::OnRender() -> void auto CMisc::OnRender() -> void
@@ -278,7 +572,19 @@ auto CMisc::RunTriggerbot( CCSGOInput* pInput , CUserCmd* pUserCmd ) -> void
return; return;
} }
const auto Result = GetCL_Trace()->TraceToHitGroup( pInput ); TriggerTraceResult_t Result;
if ( Settings::Triggerbot::SeedTrigger )
{
Vector3 vDirection;
if ( CalculateSeedDirection( pInput , pUserCmd , &vDirection ) )
Result = GetCL_Trace()->TraceDirectionToHitGroup( &vDirection );
}
else
{
Result = GetCL_Trace()->TraceToHitGroup( pInput );
}
auto* pTargetPawn = reinterpret_cast<C_CSPlayerPawn*>( Result.pHitEntity ); auto* pTargetPawn = reinterpret_cast<C_CSPlayerPawn*>( Result.pHitEntity );
@@ -300,7 +606,7 @@ auto CMisc::RunTriggerbot( CCSGOInput* pInput , CUserCmd* pUserCmd ) -> void
default: bTargetValid = true; break; default: bTargetValid = true; break;
} }
if ( bTargetValid && Settings::Triggerbot::Hitchance > 0 ) if ( bTargetValid && !Settings::Triggerbot::SeedTrigger && Settings::Triggerbot::Hitchance > 0 )
{ {
const QAngle ViewAngles = *CCSGOInput_GetViewAngles( pInput , 0 ); const QAngle ViewAngles = *CCSGOInput_GetViewAngles( pInput , 0 );
@@ -410,6 +716,50 @@ auto CMisc::CalculateHitchance( CCSGOInput* pInput , C_CSPlayerPawn* pTarget , c
return static_cast<float>( Hits ) / static_cast<float>( Samples ); return static_cast<float>( Hits ) / static_cast<float>( Samples );
} }
auto CMisc::CalculateSeedDirection( CCSGOInput* pInput , CUserCmd* pUserCmd , Vector3* vOutDirection ) -> bool
{
if ( !pInput || !vOutDirection )
return false;
auto* pWeapon = GetCL_Weapons()->GetLocalActiveWeapon();
if ( !pWeapon )
return false;
const QAngle ViewAngles = *CCSGOInput_GetViewAngles( pInput , 0 );
int iSeed = 0;
if ( pUserCmd && pUserCmd->cmd.base().has_random_seed() )
{
iSeed = pUserCmd->cmd.base().random_seed();
}
else
{
int iTick = 0;
if ( pUserCmd && pUserCmd->cmd.base().has_client_tick() )
iTick = pUserCmd->cmd.base().client_tick();
iSeed = static_cast<int>( GetSpreadSeed( ViewAngles , iTick ) );
}
const float flInaccuracy = C_CSWeaponBaseGun_GetInaccuracy( pWeapon , nullptr , nullptr );
const float flSpread = C_CSWeaponBaseGun_GetSpread( pWeapon );
const auto ItemDefIdx = GetCL_Weapons()->GetLocalWeaponDefinitionIndex();
const auto sv = CalculateSeedSpread( iSeed + 1 , flInaccuracy , flSpread , ItemDefIdx );
Vector3 vForward , vRight , vUp;
Math::AngleVectors( ViewAngles , vForward , vRight , vUp );
*vOutDirection = ( vForward + vRight * -sv.x + vUp * sv.y ).Normalized();
return true;
}
auto CMisc::OnFrameStageNotify( int FrameStage ) -> void auto CMisc::OnFrameStageNotify( int FrameStage ) -> void
{ {
if ( FrameStage != 6 ) if ( FrameStage != 6 )
@@ -35,6 +35,7 @@ public:
private: private:
auto RunTriggerbot( CCSGOInput* pInput , CUserCmd* pUserCmd ) -> void; auto RunTriggerbot( CCSGOInput* pInput , CUserCmd* pUserCmd ) -> void;
auto CalculateHitchance( CCSGOInput* pInput , C_CSPlayerPawn* pTarget , const QAngle& ViewAngles , const int Samples ) -> float; auto CalculateHitchance( CCSGOInput* pInput , C_CSPlayerPawn* pTarget , const QAngle& ViewAngles , const int Samples ) -> float;
auto CalculateSeedDirection( CCSGOInput* pInput , CUserCmd* pUserCmd , Vector3* vOutDirection ) -> bool;
private: private:
std::chrono::steady_clock::time_point m_HitTime{}; std::chrono::steady_clock::time_point m_HitTime{};
@@ -393,10 +393,15 @@ auto CAndromedaMenu::RenderMiscPanel() -> void
ImGui::Separator(); ImGui::Separator();
Checkbox( "Seed-Modus (deterministisch)" , Settings::Triggerbot::SeedTrigger );
if ( !Settings::Triggerbot::SeedTrigger )
{
SliderInt( "Hitchance (%)" , Settings::Triggerbot::Hitchance , 0 , 100 , Settings::Triggerbot::Hitchance > 0 ? "%d%%" : "%d (aus)" ); SliderInt( "Hitchance (%)" , Settings::Triggerbot::Hitchance , 0 , 100 , Settings::Triggerbot::Hitchance > 0 ? "%d%%" : "%d (aus)" );
if ( Settings::Triggerbot::Hitchance > 0 ) if ( Settings::Triggerbot::Hitchance > 0 )
SliderInt( "Hitchance Samples" , Settings::Triggerbot::HitchanceSamples , 16 , 128 ); SliderInt( "Hitchance Samples" , Settings::Triggerbot::HitchanceSamples , 16 , 128 );
}
ImGui::Separator(); ImGui::Separator();
@@ -69,6 +69,7 @@ namespace Settings
inline auto DrawIndicator = true; inline auto DrawIndicator = true;
inline auto Hitchance = 0; // percent, 0 = off inline auto Hitchance = 0; // percent, 0 = off
inline auto HitchanceSamples = 64; inline auto HitchanceSamples = 64;
inline auto SeedTrigger = false; // deterministic: only fire if the real bullet (current random_seed) would hit
} }
namespace Colors namespace Colors
{ {
@@ -120,6 +120,35 @@ auto CL_Trace::TraceToHitGroup( CCSGOInput* pInput ) -> TriggerTraceResult_t
return Result; return Result;
} }
auto CL_Trace::TraceDirectionToHitGroup( const Vector3* vDirection ) -> TriggerTraceResult_t
{
TriggerTraceResult_t Result;
auto pLocalPlayerPawn = GetCL_Players()->GetLocalPlayerPawn();
if ( !pLocalPlayerPawn || !vDirection )
return Result;
Ray_t Ray;
CGameTrace GameTrace;
CTraceFilter Filter( 0x1C1003 , pLocalPlayerPawn , 3 , 15 );
const auto vStart = GetCL_Players()->GetLocalEyeOrigin();
const auto vEnd = vStart + ( *vDirection * 8192.f );
if ( IGamePhysicsQuery_TraceShape( SDK::Pointers::CVPhys2World() , Ray , vStart , vEnd , &Filter , &GameTrace ) )
{
if ( GameTrace.pHitEntity && GameTrace.pHitBox )
{
Result.pHitEntity = GameTrace.pHitEntity;
Result.nHitGroup = GameTrace.pHitBox->nGroupId;
}
}
return Result;
}
auto GetCL_Trace() -> CL_Trace* auto GetCL_Trace() -> CL_Trace*
{ {
return &g_CL_Trace; return &g_CL_Trace;
@@ -23,6 +23,7 @@ public:
auto TraceToBoneEntity( CCSGOInput* pInput , const QAngle* AngleCorrection , QAngle* ViewAngleCorrection ) -> std::pair<uint64_t , C_BaseEntity*>; auto TraceToBoneEntity( CCSGOInput* pInput , const QAngle* AngleCorrection , QAngle* ViewAngleCorrection ) -> std::pair<uint64_t , C_BaseEntity*>;
auto TraceToEntityEndPos( const Vector3* vEnd ) -> C_BaseEntity*; auto TraceToEntityEndPos( const Vector3* vEnd ) -> C_BaseEntity*;
auto TraceToHitGroup( CCSGOInput* pInput ) -> TriggerTraceResult_t; auto TraceToHitGroup( CCSGOInput* pInput ) -> TriggerTraceResult_t;
auto TraceDirectionToHitGroup( const Vector3* vDirection ) -> TriggerTraceResult_t;
}; };
auto GetCL_Trace() -> CL_Trace*; auto GetCL_Trace() -> CL_Trace*;