From 693963aa8ed02d6ed9cb60c8115af7346b2a3f18 Mon Sep 17 00:00:00 2001
From: Hiatus <78707555+backfromhiatus@users.noreply.github.com>
Date: Tue, 7 Apr 2026 21:57:15 -0700
Subject: [PATCH] A lot
- Config system
- Skinchanger (yay, but insta dtc)
- Movement features (Bhop and Quickstop)
- Smoke voxel parsing for smoke check
- Fixed lots of bugs with zui
- Damage indicator, hit marker, hitsounds
- Added RCS to the aimbot (its so bad tho idk why)
- A horrible seed triggerbot (fix next update)
- Newly reversed get_inaccuracy function
- Made the penetration crosshair more like Aimware's with the numbers thingy
- Magnet triggerbot (locks at head)
- Better looking menu layout
- Chams (with 4 "materials")
- Fov changer (writes so is dtc)
---
catalyst/catalyst.vcxproj | 123 +-
catalyst/catalyst.vcxproj.filters | 275 +-
catalyst/project/core/config/config.cpp | 635 +
catalyst/project/core/config/config.hpp | 40 +
catalyst/project/core/features/features.hpp | 253 +-
.../core/features/impl/combat/legit.cpp | 1404 +-
.../core/features/impl/combat/shared.cpp | 490 +-
.../core/features/impl/esp/footsteps.cpp | 148 +
.../project/core/features/impl/esp/item.cpp | 6 +-
.../project/core/features/impl/esp/player.cpp | 832 +-
.../core/features/impl/esp/projectile.cpp | 633 +-
.../core/features/impl/misc/grenades.cpp | 88 +-
.../core/features/impl/misc/impacts.cpp | 115 +-
.../project/core/features/impl/misc/misc.cpp | 411 +
.../core/features/impl/misc/nade_helper.cpp | 279 +
.../project/core/features/impl/misc/sounds.h | 16544 ++++++++++++++
.../core/features/impl/movement/movement.cpp | 97 +
.../features/impl/skinchanger/skinchanger.cpp | 431 +
catalyst/project/core/menu/menu.cpp | 933 +-
catalyst/project/core/menu/menu.hpp | 12 +-
catalyst/project/core/render/render.cpp | 53 +-
catalyst/project/core/render/render.hpp | 2 +
catalyst/project/core/settings.hpp | 274 +-
catalyst/project/core/systems/impl/bvh.cpp | 9 +-
.../project/core/systems/impl/collector.cpp | 75 +-
.../project/core/systems/impl/convars.cpp | 2 +-
.../project/core/systems/impl/entities.cpp | 7 +-
catalyst/project/core/systems/impl/local.cpp | 15 +
catalyst/project/core/systems/impl/view.cpp | 45 +-
catalyst/project/core/systems/impl/voxels.cpp | 209 +
catalyst/project/core/systems/systems.hpp | 50 +-
catalyst/project/core/threads/threads.cpp | 52 +-
catalyst/project/core/threads/threads.hpp | 2 +
catalyst/project/entry.cpp | 15 +-
catalyst/project/external/curl/curl.h | 3260 +++
catalyst/project/external/curl/curlver.h | 79 +
catalyst/project/external/curl/easy.h | 125 +
catalyst/project/external/curl/header.h | 74 +
catalyst/project/external/curl/mprintf.h | 85 +
catalyst/project/external/curl/multi.h | 485 +
catalyst/project/external/curl/options.h | 70 +
catalyst/project/external/curl/stdcheaders.h | 35 +
catalyst/project/external/curl/system.h | 496 +
.../project/external/curl/typecheck-gcc.h | 718 +
catalyst/project/external/curl/urlapi.h | 155 +
catalyst/project/external/curl/websockets.h | 84 +
catalyst/project/external/nlohmann/json.hpp | 18912 ++++++++++++++++
catalyst/project/external/zdraw/zdraw.cpp | 4 +-
catalyst/project/external/zdraw/zdraw.hpp | 5 +-
catalyst/project/external/zdraw/zui/zui.cpp | 392 +-
catalyst/project/stdafx.hpp | 14 +-
catalyst/project/utilities/a2x/client_dll.hpp | 7915 +++++++
catalyst/project/utilities/a2x/offsets.hpp | 59 +
catalyst/project/utilities/cstypes.hpp | 2 -
.../project/utilities/input/csgoinput.cpp | 72 +
.../project/utilities/input/csgoinput.hpp | 11 +
catalyst/project/utilities/math/math.cpp | 29 +
catalyst/project/utilities/math/math.hpp | 3 +
catalyst/project/utilities/memory/memory.cpp | 34 +-
catalyst/project/utilities/memory/memory.hpp | 45 +-
.../project/utilities/offsets/offsets.cpp | 4 +
.../project/utilities/offsets/offsets.hpp | 1 +
catalyst/project/utilities/random.hpp | 14 +-
catalyst/project/utilities/timing/timing.cpp | 81 -
catalyst/project/utilities/timing/timing.hpp | 31 -
65 files changed, 56084 insertions(+), 1769 deletions(-)
create mode 100644 catalyst/project/core/config/config.cpp
create mode 100644 catalyst/project/core/config/config.hpp
create mode 100644 catalyst/project/core/features/impl/esp/footsteps.cpp
create mode 100644 catalyst/project/core/features/impl/misc/misc.cpp
create mode 100644 catalyst/project/core/features/impl/misc/nade_helper.cpp
create mode 100644 catalyst/project/core/features/impl/misc/sounds.h
create mode 100644 catalyst/project/core/features/impl/movement/movement.cpp
create mode 100644 catalyst/project/core/features/impl/skinchanger/skinchanger.cpp
create mode 100644 catalyst/project/core/systems/impl/voxels.cpp
create mode 100644 catalyst/project/external/curl/curl.h
create mode 100644 catalyst/project/external/curl/curlver.h
create mode 100644 catalyst/project/external/curl/easy.h
create mode 100644 catalyst/project/external/curl/header.h
create mode 100644 catalyst/project/external/curl/mprintf.h
create mode 100644 catalyst/project/external/curl/multi.h
create mode 100644 catalyst/project/external/curl/options.h
create mode 100644 catalyst/project/external/curl/stdcheaders.h
create mode 100644 catalyst/project/external/curl/system.h
create mode 100644 catalyst/project/external/curl/typecheck-gcc.h
create mode 100644 catalyst/project/external/curl/urlapi.h
create mode 100644 catalyst/project/external/curl/websockets.h
create mode 100644 catalyst/project/external/nlohmann/json.hpp
create mode 100644 catalyst/project/utilities/a2x/client_dll.hpp
create mode 100644 catalyst/project/utilities/a2x/offsets.hpp
create mode 100644 catalyst/project/utilities/input/csgoinput.cpp
create mode 100644 catalyst/project/utilities/input/csgoinput.hpp
delete mode 100644 catalyst/project/utilities/timing/timing.cpp
delete mode 100644 catalyst/project/utilities/timing/timing.hpp
diff --git a/catalyst/catalyst.vcxproj b/catalyst/catalyst.vcxproj
index d4fac59..c74eadf 100644
--- a/catalyst/catalyst.vcxproj
+++ b/catalyst/catalyst.vcxproj
@@ -19,7 +19,7 @@
false
v145
true
- Unicode
+ MultiByte
@@ -58,27 +58,28 @@
+
+
+
+
+
+
-
- NotUsing
-
-
- NotUsing
-
+
Create
@@ -95,70 +96,40 @@
-
+
+
+ NotUsing
+
+
+ NotUsing
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -219,27 +190,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/catalyst/catalyst.vcxproj.filters b/catalyst/catalyst.vcxproj.filters
index b47ce0e..575634e 100644
--- a/catalyst/catalyst.vcxproj.filters
+++ b/catalyst/catalyst.vcxproj.filters
@@ -60,6 +60,9 @@
Source Files
+
+ Source Files
+
Source Files
@@ -69,6 +72,12 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
Source Files
@@ -93,22 +102,34 @@
Source Files
+
+ Source Files
+
Source Files
Source Files
-
+
+ Source Files
+
+
Source Files
Source Files
-
+
Source Files
-
+
+ Source Files
+
+
+ Source Files
+
+
Source Files
@@ -152,213 +173,6 @@
Header Files
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
Header Files
@@ -533,10 +347,49 @@
Header Files
+
+ Header Files
+
Header Files
-
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
Header Files
diff --git a/catalyst/project/core/config/config.cpp b/catalyst/project/core/config/config.cpp
new file mode 100644
index 0000000..45c3777
--- /dev/null
+++ b/catalyst/project/core/config/config.cpp
@@ -0,0 +1,635 @@
+#include
+#include
+#include
+#include
+#include
+
+#include "config.hpp"
+
+namespace fs = std::filesystem;
+
+void config_system::init( )
+{
+ char path[ MAX_PATH ];
+ if ( SUCCEEDED( SHGetFolderPathA( NULL, CSIDL_MYDOCUMENTS, NULL, 0, path ) ) )
+ {
+ this->m_path = fs::path( path ) / "catalyst";
+
+ if ( !fs::exists( this->m_path ) )
+ {
+ fs::create_directory( this->m_path );
+ }
+ }
+
+ this->refresh_list( );
+ this->initialized = true;
+}
+
+void config_system::refresh_list( )
+{
+ this->m_configs.clear( );
+
+ if ( !fs::exists( this->m_path ) )
+ {
+ return;
+ }
+
+ for ( const auto& entry : fs::directory_iterator( this->m_path ) )
+ {
+ if ( entry.is_regular_file( ) && entry.path( ).extension( ) == ".cfg" )
+ {
+ this->m_configs.push_back( entry.path( ).stem( ).string( ) );
+ }
+ }
+}
+
+template
+static void write( std::ofstream& out, std::string_view name, const T& value )
+{
+ out << name << "=" << value << "\n";
+}
+
+static void write( std::ofstream& out, std::string_view name, const zdraw::rgba& color )
+{
+ out << name << "=" << (int)color.r << "," << (int)color.g << "," << (int)color.b << "," << (int)color.a << "\n";
+}
+
+template requires std::is_enum_v
+static void write( std::ofstream& out, std::string_view name, const T& value )
+{
+ out << name << "=" << static_cast( value ) << "\n";
+}
+
+void config_system::save( std::string_view name )
+{
+ if ( name.empty( ) ) return;
+
+ fs::path file = this->m_path / ( std::string( name ) + ".cfg" );
+ std::ofstream out( file );
+
+ if ( !out.is_open( ) ) return;
+
+ for ( std::uint32_t i = 0; i < settings::combat::k_group_count; ++i )
+ {
+ auto& cfg = settings::g_combat.groups[ i ];
+ std::string prefix = std::format( "combat.group_{}.", i );
+
+ write( out, prefix + "aimbot.enabled", cfg.aimbot.enabled );
+ write( out, prefix + "aimbot.key", cfg.aimbot.key );
+ write( out, prefix + "aimbot.type", cfg.aimbot.type );
+ write( out, prefix + "aimbot.fov", cfg.aimbot.fov );
+ write( out, prefix + "aimbot.smoothing", cfg.aimbot.smoothing );
+ write( out, prefix + "aimbot.autowall", cfg.aimbot.autowall );
+ write( out, prefix + "aimbot.min_damage", cfg.aimbot.min_damage );
+ write( out, prefix + "aimbot.visible_only", cfg.aimbot.visible_only );
+ write( out, prefix + "aimbot.smoke_check", cfg.aimbot.smoke_check );
+ write( out, prefix + "aimbot.draw_fov", cfg.aimbot.draw_fov );
+ write( out, prefix + "aimbot.fov_color", cfg.aimbot.fov_color );
+ write( out, prefix + "aimbot.autowall_info", cfg.aimbot.autowall_info );
+ write( out, prefix + "aimbot.autowall_info_color", cfg.aimbot.autowall_info_color );
+ write( out, prefix + "aimbot.predictive", cfg.aimbot.predictive );
+ write( out, prefix + "aimbot.rcs", cfg.aimbot.rcs );
+ write( out, prefix + "aimbot.silent", cfg.aimbot.silent );
+ write( out, prefix + "aimbot.rcs_factor", cfg.aimbot.rcs_factor );
+
+ write( out, prefix + "aimbot.hitgroups.head", cfg.aimbot.hitgroups.head );
+ write( out, prefix + "aimbot.hitgroups.chest", cfg.aimbot.hitgroups.chest );
+ write( out, prefix + "aimbot.hitgroups.stomach", cfg.aimbot.hitgroups.stomach );
+ write( out, prefix + "aimbot.hitgroups.arms", cfg.aimbot.hitgroups.arms );
+ write( out, prefix + "aimbot.hitgroups.legs", cfg.aimbot.hitgroups.legs );
+
+ write( out, prefix + "aimbot.multipoint", cfg.aimbot.multipoint );
+ write( out, prefix + "aimbot.multipoint_scale", cfg.aimbot.multipoint_scale );
+
+ write( out, prefix + "triggerbot.enabled", cfg.triggerbot.enabled );
+ write( out, prefix + "triggerbot.key", cfg.triggerbot.key );
+ write( out, prefix + "triggerbot.seed_triggerbot", cfg.triggerbot.seed_triggerbot );
+ write( out, prefix + "triggerbot.show_spread", cfg.triggerbot.show_spread );
+ write( out, prefix + "triggerbot.hitchance", cfg.triggerbot.hitchance );
+ write( out, prefix + "triggerbot.delay", cfg.triggerbot.delay );
+
+ write( out, prefix + "triggerbot.hitgroups.head", cfg.triggerbot.hitgroups.head );
+ write( out, prefix + "triggerbot.hitgroups.chest", cfg.triggerbot.hitgroups.chest );
+ write( out, prefix + "triggerbot.hitgroups.stomach", cfg.triggerbot.hitgroups.stomach );
+ write( out, prefix + "triggerbot.hitgroups.arms", cfg.triggerbot.hitgroups.arms );
+ write( out, prefix + "triggerbot.hitgroups.legs", cfg.triggerbot.hitgroups.legs );
+
+ write( out, prefix + "triggerbot.autowall", cfg.triggerbot.autowall );
+ write( out, prefix + "triggerbot.min_damage", cfg.triggerbot.min_damage );
+ write( out, prefix + "triggerbot.autostop", cfg.triggerbot.autostop );
+ write( out, prefix + "triggerbot.early_autostop", cfg.triggerbot.early_autostop );
+ write( out, prefix + "triggerbot.predictive", cfg.triggerbot.predictive );
+ write( out, prefix + "triggerbot.predictive_ms", cfg.triggerbot.predictive_ms );
+ write( out, prefix + "triggerbot.predictive_visualize", cfg.triggerbot.predictive_visualize );
+ write( out, prefix + "triggerbot.draw_penetration_crosshair", cfg.other.penetration_crosshair );
+ write( out, prefix + "triggerbot.draw_penetration_damage", cfg.other.penetration_damage );
+ write( out, prefix + "triggerbot.penetration_color_yes", cfg.other.penetration_color_yes );
+ write( out, prefix + "triggerbot.penetration_color_no", cfg.other.penetration_color_no );
+ write( out, prefix + "triggerbot.magnet", cfg.triggerbot.magnet );
+ write( out, prefix + "triggerbot.magnet_smoothing", cfg.triggerbot.magnet_smoothing );
+ }
+
+ // ESP - Player
+ auto& p = settings::g_esp.m_player;
+ write( out, "esp.player.enabled", p.enabled );
+ write( out, "esp.player.box.enabled", p.m_box.enabled );
+ write( out, "esp.player.box.style", p.m_box.style );
+ write( out, "esp.player.box.fill", p.m_box.fill );
+ write( out, "esp.player.box.outline", p.m_box.outline );
+ write( out, "esp.player.box.corner_length", p.m_box.corner_length );
+ write( out, "esp.player.box.visible_color", p.m_box.visible_color );
+ write( out, "esp.player.box.occluded_color", p.m_box.occluded_color );
+
+ write( out, "esp.player.skeleton.enabled", p.m_skeleton.enabled );
+ write( out, "esp.player.skeleton.rounded", p.m_skeleton.rounded );
+ write( out, "esp.player.skeleton.thickness", p.m_skeleton.thickness );
+ write( out, "esp.player.skeleton.visible_color", p.m_skeleton.visible_color );
+ write( out, "esp.player.skeleton.occluded_color", p.m_skeleton.occluded_color );
+
+ write( out, "esp.player.hitboxes.enabled", p.m_hitboxes.enabled );
+ write( out, "esp.player.hitboxes.mode", p.m_hitboxes.mode );
+ write( out, "esp.player.hitboxes.visible_color", p.m_hitboxes.visible_color );
+ write( out, "esp.player.hitboxes.occluded_color", p.m_hitboxes.occluded_color );
+ write( out, "esp.player.hitboxes.outline_color", p.m_hitboxes.outline_color );
+ write( out, "esp.player.hitboxes.fill", p.m_hitboxes.fill );
+ write( out, "esp.player.hitboxes.outline", p.m_hitboxes.outline );
+ write( out, "esp.player.hitboxes.health_indicator", p.m_hitboxes.health_indicator );
+
+ write( out, "esp.player.health_bar.enabled", p.m_health_bar.enabled );
+ write( out, "esp.player.health_bar.position", p.m_health_bar.position );
+ write( out, "esp.player.health_bar.outline", p.m_health_bar.outline );
+ write( out, "esp.player.health_bar.gradient", p.m_health_bar.gradient );
+ write( out, "esp.player.health_bar.show_value", p.m_health_bar.show_value );
+ write( out, "esp.player.health_bar.full_color", p.m_health_bar.full_color );
+ write( out, "esp.player.health_bar.low_color", p.m_health_bar.low_color );
+
+ write( out, "esp.player.ammo_bar.enabled", p.m_ammo_bar.enabled );
+ write( out, "esp.player.ammo_bar.position", p.m_ammo_bar.position );
+ write( out, "esp.player.ammo_bar.outline", p.m_ammo_bar.outline );
+ write( out, "esp.player.ammo_bar.gradient", p.m_ammo_bar.gradient );
+ write( out, "esp.player.ammo_bar.show_value", p.m_ammo_bar.show_value );
+ write( out, "esp.player.ammo_bar.full_color", p.m_ammo_bar.full_color );
+ write( out, "esp.player.ammo_bar.low_color", p.m_ammo_bar.low_color );
+
+ write( out, "esp.player.info_flags.enabled", p.m_info_flags.enabled );
+ write( out, "esp.player.info_flags.flags", (int)p.m_info_flags.flags );
+
+ write( out, "esp.player.name.enabled", p.m_name.enabled );
+ write( out, "esp.player.name.color", p.m_name.color );
+
+ write( out, "esp.player.weapon.enabled", p.m_weapon.enabled );
+ write( out, "esp.player.weapon.display", p.m_weapon.display );
+ write( out, "esp.player.weapon.text_color", p.m_weapon.text_color );
+ write( out, "esp.player.weapon.icon_color", p.m_weapon.icon_color );
+
+ write( out, "esp.player.trails.enabled", p.m_trails.enabled );
+ write( out, "esp.player.trails.local", p.m_trails.local );
+ write( out, "esp.player.trails.enemy", p.m_trails.enemy );
+ write( out, "esp.player.trails.team", p.m_trails.team );
+ write( out, "esp.player.trails.thickness", p.m_trails.thickness );
+ write( out, "esp.player.trails.max_points", p.m_trails.max_points );
+ write( out, "esp.player.trails.local_color", p.m_trails.local_color );
+ write( out, "esp.player.trails.enemy_color", p.m_trails.enemy_color );
+ write( out, "esp.player.trails.team_color", p.m_trails.team_color );
+
+ write( out, "esp.player.footsteps.enabled", p.m_footsteps.enabled );
+ write( out, "esp.player.footsteps.show_teammates", p.m_footsteps.show_teammates );
+ write( out, "esp.player.footsteps.footstep_max_radius", p.m_footsteps.footstep_max_radius );
+ write( out, "esp.player.footsteps.footstep_color", p.m_footsteps.footstep_color );
+ write( out, "esp.player.footsteps.jump_max_radius", p.m_footsteps.jump_max_radius );
+ write( out, "esp.player.footsteps.jump_color", p.m_footsteps.jump_color );
+ write( out, "esp.player.footsteps.land_max_radius", p.m_footsteps.land_max_radius );
+ write( out, "esp.player.footsteps.land_color", p.m_footsteps.land_color );
+ write( out, "esp.player.footsteps.expand_duration", p.m_footsteps.expand_duration );
+ write( out, "esp.player.footsteps.fade_duration", p.m_footsteps.fade_duration );
+ write( out, "esp.player.footsteps.segments", p.m_footsteps.segments );
+ write( out, "esp.player.footsteps.thickness", p.m_footsteps.thickness );
+
+ // ESP - Item
+ auto& it = settings::g_esp.m_item;
+ write( out, "esp.item.enabled", it.enabled );
+ write( out, "esp.item.max_distance", it.max_distance );
+ write( out, "esp.item.icon.enabled", it.m_icon.enabled );
+ write( out, "esp.item.icon.color", it.m_icon.color );
+ write( out, "esp.item.name.enabled", it.m_name.enabled );
+ write( out, "esp.item.name.color", it.m_name.color );
+ write( out, "esp.item.ammo.enabled", it.m_ammo.enabled );
+ write( out, "esp.item.ammo.color", it.m_ammo.color );
+ write( out, "esp.item.ammo.empty_color", it.m_ammo.empty_color );
+
+ write( out, "esp.item.filters.rifles", it.m_filters.rifles );
+ write( out, "esp.item.filters.smgs", it.m_filters.smgs );
+ write( out, "esp.item.filters.shotguns", it.m_filters.shotguns );
+ write( out, "esp.item.filters.snipers", it.m_filters.snipers );
+ write( out, "esp.item.filters.pistols", it.m_filters.pistols );
+ write( out, "esp.item.filters.heavy", it.m_filters.heavy );
+ write( out, "esp.item.filters.grenades", it.m_filters.grenades );
+ write( out, "esp.item.filters.utility", it.m_filters.utility );
+
+ // ESP - Projectile
+ auto& pr = settings::g_esp.m_projectile;
+ write( out, "esp.projectile.enabled", pr.enabled );
+ write( out, "esp.projectile.show_icon", pr.show_icon );
+ write( out, "esp.projectile.show_name", pr.show_name );
+ write( out, "esp.projectile.show_timer_bar", pr.show_timer_bar );
+ write( out, "esp.projectile.show_inferno_bounds", pr.show_inferno_bounds );
+ write( out, "esp.projectile.show_smoke_voxels", pr.show_smoke_voxels );
+ write( out, "esp.projectile.color_he", pr.color_he );
+ write( out, "esp.projectile.color_flash", pr.color_flash );
+ write( out, "esp.projectile.color_smoke", pr.color_smoke );
+ write( out, "esp.projectile.color_molotov", pr.color_molotov );
+ write( out, "esp.projectile.color_decoy", pr.color_decoy );
+
+ auto& bt = settings::g_esp.m_bullet_tracers;
+ write( out, "esp.bullet_tracers.enabled", bt.enabled );
+ write( out, "esp.bullet_tracers.thickness", bt.thickness );
+ write( out, "esp.bullet_tracers.duration", (int)bt.duration );
+ write( out, "esp.bullet_tracers.color", bt.color );
+
+ // Misc
+ auto& m = settings::g_misc.m_grenades;
+ write( out, "misc.grenades.enabled", m.enabled );
+ write( out, "misc.grenades.line_color", m.line_color );
+ write( out, "misc.grenades.line_thickness", m.line_thickness );
+ write( out, "misc.grenades.line_gradient", m.line_gradient );
+ write( out, "misc.grenades.show_bounces", m.show_bounces );
+ write( out, "misc.grenades.bounce_color", m.bounce_color );
+ write( out, "misc.grenades.bounce_size", m.bounce_size );
+ write( out, "misc.grenades.detonate_color", m.detonate_color );
+ write( out, "misc.grenades.detonate_size", m.detonate_size );
+ write( out, "misc.grenades.per_type_colors", m.per_type_colors );
+ write( out, "misc.grenades.color_he", m.color_he );
+ write( out, "misc.grenades.color_flash", m.color_flash );
+ write( out, "misc.grenades.color_smoke", m.color_smoke );
+ write( out, "misc.grenades.color_molotov", m.color_molotov );
+ write( out, "misc.grenades.color_decoy", m.color_decoy );
+ write( out, "misc.grenades.local_only", m.local_only );
+ write( out, "misc.grenades.fade_duration", m.fade_duration );
+
+ auto& nh = settings::g_misc.m_nade_helper;
+ write( out, "misc.nade_helper.enabled", nh.enabled );
+ write( out, "misc.nade_helper.show_name", nh.show_name );
+ write( out, "misc.nade_helper.show_type", nh.show_type );
+ write( out, "misc.nade_helper.stand_pos_color", nh.stand_pos_color );
+ write( out, "misc.nade_helper.aim_pos_color", nh.aim_pos_color );
+ write( out, "misc.nade_helper.text_color", nh.text_color );
+ write( out, "misc.nade_helper.stand_radius", nh.stand_radius );
+ write( out, "misc.nade_helper.aim_dot_size", nh.aim_dot_size );
+
+ auto& mm = settings::g_misc.m_main;
+ write( out, "misc.main.spectator_list", mm.spectator_list );
+ write( out, "misc.main.spectator_list_color", mm.spectator_list_color );
+ write( out, "misc.main.bomb_timer", mm.bomb_timer );
+ write( out, "misc.main.bomb_timer_color", mm.bomb_timer_color );
+ write( out, "misc.main.hitsound", mm.hitsound );
+ auto& fc = settings::g_misc.m_fov_changer;
+ write( out, "misc.fov_changer.enabled", fc.enabled );
+ write( out, "misc.fov_changer.fov", fc.fov );
+ write( out, "misc.fov_changer.disable_when_scoped", fc.disable_when_scoped );
+
+ // Movement
+ auto& mv = settings::g_movement;
+ write( out, "movement.bhop.enabled", mv.bhop.enabled );
+ write( out, "movement.quickstop.enabled", mv.quickstop.enabled );
+ write( out, "movement.quickstop.strength", mv.quickstop.strength );
+
+ // Skinchanger
+ {
+ std::shared_lock lock( settings::g_skinchanger.mutex );
+ write( out, "skinchanger.enabled", settings::g_skinchanger.enabled );
+ write( out, "skinchanger.music_kit", settings::g_skinchanger.music_kit );
+ for ( const auto& [idx, cfg] : settings::g_skinchanger.weapon_skins )
+ {
+ std::string p = std::format( "skinchanger.weapon_{}.", idx );
+ write( out, p + "paint_kit", cfg.paint_kit );
+ write( out, p + "seed", cfg.seed );
+ write( out, p + "stat_trak", cfg.stat_trak );
+ write( out, p + "wear", cfg.wear );
+ write( out, p + "uses_old_model", cfg.uses_old_model );
+ }
+ }
+
+ out.close( );
+ this->refresh_list( );
+}
+
+ static int parse_int( const std::string& s ) { try { return std::stoi( s ); } catch ( ... ) { return 0; } }
+ static float parse_float( const std::string& s ) { try { return std::stof( s ); } catch ( ... ) { return 0.0f; } }
+ static bool parse_bool( const std::string& s ) { return s == "1"; }
+ static zdraw::rgba parse_color( const std::string& s )
+ {
+ std::stringstream ss( s );
+ std::string r, g, b, a;
+ std::getline( ss, r, ',' );
+ std::getline( ss, g, ',' );
+ std::getline( ss, b, ',' );
+ std::getline( ss, a, ',' );
+ return zdraw::rgba{ ( std::uint8_t )parse_int( r ), ( std::uint8_t )parse_int( g ), ( std::uint8_t )parse_int( b ), ( std::uint8_t )parse_int( a ) };
+ }
+
+ // Config loads are seperate functions because all the nested else ifs
+ // would cause the compiler to truncate the function
+
+ void config_system::load( std::string_view name )
+ {
+ if ( name.empty( ) ) return;
+
+ fs::path file = this->m_path / ( std::string( name ) + ".cfg" );
+ if ( !fs::exists( file ) ) return;
+
+ std::ifstream in( file );
+ if ( !in.is_open( ) ) return;
+
+ std::string line;
+ while ( std::getline( in, line ) )
+ {
+ size_t pos = line.find( '=' );
+ if ( pos == std::string::npos ) continue;
+
+ std::string key = line.substr( 0, pos );
+ std::string val = line.substr( pos + 1 );
+
+ if ( key.starts_with( "combat." ) ) this->load_combat( key, val );
+ else if ( key.starts_with( "esp.player." ) ) this->load_esp_player( key, val );
+ else if ( key.starts_with( "esp.item." ) ) this->load_esp_item( key, val );
+ else if ( key.starts_with( "esp.projectile." ) ) this->load_esp_projectile( key, val );
+ else if ( key.starts_with( "esp.bullet_tracers." ) )
+ {
+ auto& bt = settings::g_esp.m_bullet_tracers;
+ if ( key == "esp.bullet_tracers.enabled" ) bt.enabled = parse_bool( val );
+ else if ( key == "esp.bullet_tracers.thickness" ) bt.thickness = parse_float( val );
+ else if ( key == "esp.bullet_tracers.duration" ) bt.duration = parse_float( val );
+ else if ( key == "esp.bullet_tracers.color" ) bt.color = parse_color( val );
+ }
+ else if ( key.starts_with( "misc." ) ) this->load_misc( key, val );
+ else if ( key.starts_with( "movement." ) ) this->load_movement( key, val );
+ else if ( key.starts_with( "skinchanger." ) ) this->load_skinchanger( key, val );
+ }
+
+ in.close( );
+ }
+
+ void config_system::load_combat( const std::string& key, const std::string& val )
+ {
+ if ( key.starts_with( "combat.group_" ) )
+ {
+ int group_idx = std::stoi( key.substr( 13, 1 ) );
+ if ( group_idx >= 0 && group_idx < settings::combat::k_group_count )
+ {
+ auto& cfg = settings::g_combat.groups[ group_idx ];
+ std::string subkey = key.substr( 15 );
+
+ if ( subkey == "aimbot.enabled" ) cfg.aimbot.enabled = parse_bool( val );
+ else if ( subkey == "aimbot.key" ) cfg.aimbot.key = parse_int( val );
+ else if ( subkey == "aimbot.type" ) cfg.aimbot.type = parse_int( val );
+ else if ( subkey == "aimbot.fov" ) cfg.aimbot.fov = parse_int( val );
+ else if ( subkey == "aimbot.smoothing" ) cfg.aimbot.smoothing = parse_int( val );
+ else if ( subkey == "aimbot.autowall" ) cfg.aimbot.autowall = parse_bool( val );
+ else if ( subkey == "aimbot.min_damage" ) cfg.aimbot.min_damage = parse_float( val );
+ else if ( subkey == "aimbot.visible_only" ) cfg.aimbot.visible_only = parse_bool( val );
+ else if ( subkey == "aimbot.smoke_check" ) cfg.aimbot.smoke_check = parse_bool( val );
+ else if ( subkey == "aimbot.draw_fov" ) cfg.aimbot.draw_fov = parse_bool( val );
+ else if ( subkey == "aimbot.fov_color" ) cfg.aimbot.fov_color = parse_color( val );
+ else if ( subkey == "aimbot.autowall_info" ) cfg.aimbot.autowall_info = parse_bool( val );
+ else if ( subkey == "aimbot.autowall_info_color" ) cfg.aimbot.autowall_info_color = parse_color( val );
+ else if ( subkey == "aimbot.predictive" ) cfg.aimbot.predictive = parse_bool( val );
+ else if ( subkey == "aimbot.rcs" ) cfg.aimbot.rcs = parse_bool( val );
+ else if ( subkey == "aimbot.silent" ) cfg.aimbot.silent = parse_bool( val );
+ else if ( subkey == "aimbot.rcs_factor" ) cfg.aimbot.rcs_factor = parse_float( val );
+ else if ( subkey == "aimbot.hitgroups.head" ) cfg.aimbot.hitgroups.head = parse_bool( val );
+ else if ( subkey == "aimbot.hitgroups.chest" ) cfg.aimbot.hitgroups.chest = parse_bool( val );
+ else if ( subkey == "aimbot.hitgroups.stomach" ) cfg.aimbot.hitgroups.stomach = parse_bool( val );
+ else if ( subkey == "aimbot.hitgroups.arms" ) cfg.aimbot.hitgroups.arms = parse_bool( val );
+ else if ( subkey == "aimbot.hitgroups.legs" ) cfg.aimbot.hitgroups.legs = parse_bool( val );
+ else if ( subkey == "aimbot.multipoint" ) cfg.aimbot.multipoint = parse_bool( val );
+ else if ( subkey == "aimbot.multipoint_scale" ) cfg.aimbot.multipoint_scale = parse_float( val );
+ else if ( subkey == "triggerbot.enabled" ) cfg.triggerbot.enabled = parse_bool( val );
+ else if ( subkey == "triggerbot.key" ) cfg.triggerbot.key = parse_int( val );
+ else if ( subkey == "triggerbot.seed_triggerbot" ) cfg.triggerbot.seed_triggerbot = parse_bool( val );
+ else if ( subkey == "triggerbot.show_spread" ) cfg.triggerbot.show_spread = parse_bool( val );
+ else if ( subkey == "triggerbot.hitchance" ) cfg.triggerbot.hitchance = parse_float( val );
+ else if ( subkey == "triggerbot.delay" ) cfg.triggerbot.delay = parse_int( val );
+ else if ( subkey == "triggerbot.hitgroups.head" ) cfg.triggerbot.hitgroups.head = parse_bool( val );
+ else if ( subkey == "triggerbot.hitgroups.chest" ) cfg.triggerbot.hitgroups.chest = parse_bool( val );
+ else if ( subkey == "triggerbot.hitgroups.stomach" ) cfg.triggerbot.hitgroups.stomach = parse_bool( val );
+ else if ( subkey == "triggerbot.hitgroups.arms" ) cfg.triggerbot.hitgroups.arms = parse_bool( val );
+ else if ( subkey == "triggerbot.hitgroups.legs" ) cfg.triggerbot.hitgroups.legs = parse_bool( val );
+ else if ( subkey == "triggerbot.autowall" ) cfg.triggerbot.autowall = parse_bool( val );
+ else if ( subkey == "triggerbot.min_damage" ) cfg.triggerbot.min_damage = parse_float( val );
+ else if ( subkey == "triggerbot.autostop" ) cfg.triggerbot.autostop = parse_bool( val );
+ else if ( subkey == "triggerbot.early_autostop" ) cfg.triggerbot.early_autostop = parse_bool( val );
+ else if ( subkey == "triggerbot.predictive" ) cfg.triggerbot.predictive = parse_bool( val );
+ else if ( subkey == "triggerbot.predictive_ms" ) cfg.triggerbot.predictive_ms = parse_int( val );
+ else if ( subkey == "triggerbot.predictive_visualize" ) cfg.triggerbot.predictive_visualize = parse_bool( val );
+ else if ( subkey == "triggerbot.draw_penetration_crosshair" ) cfg.other.penetration_crosshair = parse_bool( val );
+ else if ( subkey == "triggerbot.draw_penetration_damage" ) cfg.other.penetration_damage = parse_bool( val );
+ else if ( subkey == "triggerbot.penetration_color_yes" ) cfg.other.penetration_color_yes = parse_color( val );
+ else if ( subkey == "triggerbot.penetration_color_no" ) cfg.other.penetration_color_no = parse_color( val );
+ else if ( subkey == "triggerbot.magnet" ) cfg.triggerbot.magnet = parse_bool( val );
+ else if ( subkey == "triggerbot.magnet_smoothing" ) cfg.triggerbot.magnet_smoothing = parse_float( val );
+ }
+ }
+ }
+
+ void config_system::load_esp_player( const std::string& key, const std::string& val )
+ {
+ auto& p = settings::g_esp.m_player;
+ if ( key == "esp.player.enabled" ) p.enabled = parse_bool( val );
+ else if ( key == "esp.player.box.enabled" ) p.m_box.enabled = parse_bool( val );
+ else if ( key == "esp.player.box.style" ) p.m_box.style = parse_int( val );
+ else if ( key == "esp.player.box.fill" ) p.m_box.fill = parse_bool( val );
+ else if ( key == "esp.player.box.outline" ) p.m_box.outline = parse_bool( val );
+ else if ( key == "esp.player.box.corner_length" ) p.m_box.corner_length = parse_float( val );
+ else if ( key == "esp.player.box.visible_color" ) p.m_box.visible_color = parse_color( val );
+ else if ( key == "esp.player.box.occluded_color" ) p.m_box.occluded_color = parse_color( val );
+ else if ( key == "esp.player.skeleton.enabled" ) p.m_skeleton.enabled = parse_bool( val );
+ else if ( key == "esp.player.skeleton.rounded" ) p.m_skeleton.rounded = parse_bool( val );
+ else if ( key == "esp.player.skeleton.thickness" ) p.m_skeleton.thickness = parse_float( val );
+ else if ( key == "esp.player.skeleton.visible_color" ) p.m_skeleton.visible_color = parse_color( val );
+ else if ( key == "esp.player.skeleton.occluded_color" ) p.m_skeleton.occluded_color = parse_color( val );
+ else if ( key == "esp.player.hitboxes.enabled" ) p.m_hitboxes.enabled = parse_bool( val );
+ else if ( key == "esp.player.hitboxes.mode" ) p.m_hitboxes.mode = (settings::esp::player::hitboxes::material)parse_int( val );
+ else if ( key == "esp.player.hitboxes.visible_color" ) p.m_hitboxes.visible_color = parse_color( val );
+ else if ( key == "esp.player.hitboxes.occluded_color" ) p.m_hitboxes.occluded_color = parse_color( val );
+ else if ( key == "esp.player.hitboxes.outline_color" ) p.m_hitboxes.outline_color = parse_color( val );
+ else if ( key == "esp.player.hitboxes.fill" ) p.m_hitboxes.fill = parse_bool( val );
+ else if ( key == "esp.player.hitboxes.outline" ) p.m_hitboxes.outline = parse_bool( val );
+ else if ( key == "esp.player.hitboxes.health_indicator" ) p.m_hitboxes.health_indicator = parse_bool( val );
+ else if ( key == "esp.player.health_bar.enabled" ) p.m_health_bar.enabled = parse_bool( val );
+ else if ( key == "esp.player.health_bar.position" ) p.m_health_bar.position = parse_int( val );
+ else if ( key == "esp.player.health_bar.outline" ) p.m_health_bar.outline = parse_bool( val );
+ else if ( key == "esp.player.health_bar.gradient" ) p.m_health_bar.gradient = parse_bool( val );
+ else if ( key == "esp.player.health_bar.show_value" ) p.m_health_bar.show_value = parse_bool( val );
+ else if ( key == "esp.player.health_bar.full_color" ) p.m_health_bar.full_color = parse_color( val );
+ else if ( key == "esp.player.health_bar.low_color" ) p.m_health_bar.low_color = parse_color( val );
+ else if ( key == "esp.player.ammo_bar.enabled" ) p.m_ammo_bar.enabled = parse_bool( val );
+ else if ( key == "esp.player.ammo_bar.position" ) p.m_ammo_bar.position = parse_int( val );
+ else if ( key == "esp.player.ammo_bar.outline" ) p.m_ammo_bar.outline = parse_bool( val );
+ else if ( key == "esp.player.ammo_bar.gradient" ) p.m_ammo_bar.gradient = parse_bool( val );
+ else if ( key == "esp.player.ammo_bar.show_value" ) p.m_ammo_bar.show_value = parse_bool( val );
+ else if ( key == "esp.player.ammo_bar.full_color" ) p.m_ammo_bar.full_color = parse_color( val );
+ else if ( key == "esp.player.ammo_bar.low_color" ) p.m_ammo_bar.low_color = parse_color( val );
+ else if ( key == "esp.player.info_flags.enabled" ) p.m_info_flags.enabled = parse_bool( val );
+ else if ( key == "esp.player.info_flags.flags" ) p.m_info_flags.flags = ( std::uint8_t )parse_int( val );
+ else if ( key == "esp.player.name.enabled" ) p.m_name.enabled = parse_bool( val );
+ else if ( key == "esp.player.name.color" ) p.m_name.color = parse_color( val );
+ else if ( key == "esp.player.weapon.enabled" ) p.m_weapon.enabled = parse_bool( val );
+ else if ( key == "esp.player.weapon.display" ) p.m_weapon.display = parse_int( val );
+ else if ( key == "esp.player.weapon.text_color" ) p.m_weapon.text_color = parse_color( val );
+ else if ( key == "esp.player.weapon.icon_color" ) p.m_weapon.icon_color = parse_color( val );
+ else if ( key == "esp.player.trails.enabled" ) p.m_trails.enabled = parse_bool( val );
+ else if ( key == "esp.player.trails.local" ) p.m_trails.local = parse_bool( val );
+ else if ( key == "esp.player.trails.enemy" ) p.m_trails.enemy = parse_bool( val );
+ else if ( key == "esp.player.trails.team" ) p.m_trails.team = parse_bool( val );
+ else if ( key == "esp.player.trails.thickness" ) p.m_trails.thickness = parse_float( val );
+ else if ( key == "esp.player.trails.max_points" ) p.m_trails.max_points = parse_int( val );
+ else if ( key == "esp.player.trails.local_color" ) p.m_trails.local_color = parse_color( val );
+ else if ( key == "esp.player.trails.enemy_color" ) p.m_trails.enemy_color = parse_color( val );
+ else if ( key == "esp.player.trails.team_color" ) p.m_trails.team_color = parse_color( val );
+ else if ( key == "esp.player.footsteps.enabled" ) p.m_footsteps.enabled = parse_bool( val );
+ else if ( key == "esp.player.footsteps.show_teammates" ) p.m_footsteps.show_teammates = parse_bool( val );
+ else if ( key == "esp.player.footsteps.footstep_max_radius" ) p.m_footsteps.footstep_max_radius = parse_float( val );
+ else if ( key == "esp.player.footsteps.footstep_color" ) p.m_footsteps.footstep_color = parse_color( val );
+ else if ( key == "esp.player.footsteps.jump_max_radius" ) p.m_footsteps.jump_max_radius = parse_float( val );
+ else if ( key == "esp.player.footsteps.jump_color" ) p.m_footsteps.jump_color = parse_color( val );
+ else if ( key == "esp.player.footsteps.land_max_radius" ) p.m_footsteps.land_max_radius = parse_float( val );
+ else if ( key == "esp.player.footsteps.land_color" ) p.m_footsteps.land_color = parse_color( val );
+ else if ( key == "esp.player.footsteps.expand_duration" ) p.m_footsteps.expand_duration = parse_float( val );
+ else if ( key == "esp.player.footsteps.fade_duration" ) p.m_footsteps.fade_duration = parse_float( val );
+ else if ( key == "esp.player.footsteps.segments" ) p.m_footsteps.segments = parse_int( val );
+ else if ( key == "esp.player.footsteps.thickness" ) p.m_footsteps.thickness = parse_float( val );
+ }
+
+ void config_system::load_esp_item( const std::string& key, const std::string& val )
+ {
+ auto& it = settings::g_esp.m_item;
+ if ( key == "esp.item.enabled" ) it.enabled = parse_bool( val );
+ else if ( key == "esp.item.max_distance" ) it.max_distance = parse_float( val );
+ else if ( key == "esp.item.icon.enabled" ) it.m_icon.enabled = parse_bool( val );
+ else if ( key == "esp.item.icon.color" ) it.m_icon.color = parse_color( val );
+ else if ( key == "esp.item.name.enabled" ) it.m_name.enabled = parse_bool( val );
+ else if ( key == "esp.item.name.color" ) it.m_name.color = parse_color( val );
+ else if ( key == "esp.item.ammo.enabled" ) it.m_ammo.enabled = parse_bool( val );
+ else if ( key == "esp.item.ammo.color" ) it.m_ammo.color = parse_color( val );
+ else if ( key == "esp.item.ammo.empty_color" ) it.m_ammo.empty_color = parse_color( val );
+ else if ( key == "esp.item.filters.rifles" ) it.m_filters.rifles = parse_bool( val );
+ else if ( key == "esp.item.filters.smgs" ) it.m_filters.smgs = parse_bool( val );
+ else if ( key == "esp.item.filters.shotguns" ) it.m_filters.shotguns = parse_bool( val );
+ else if ( key == "esp.item.filters.snipers" ) it.m_filters.snipers = parse_bool( val );
+ else if ( key == "esp.item.filters.pistols" ) it.m_filters.pistols = parse_bool( val );
+ else if ( key == "esp.item.filters.heavy" ) it.m_filters.heavy = parse_bool( val );
+ else if ( key == "esp.item.filters.grenades" ) it.m_filters.grenades = parse_bool( val );
+ else if ( key == "esp.item.filters.utility" ) it.m_filters.utility = parse_bool( val );
+ }
+
+ void config_system::load_esp_projectile( const std::string& key, const std::string& val )
+ {
+ auto& pr = settings::g_esp.m_projectile;
+ if ( key == "esp.projectile.enabled" ) pr.enabled = parse_bool( val );
+ else if ( key == "esp.projectile.show_icon" ) pr.show_icon = parse_bool( val );
+ else if ( key == "esp.projectile.show_name" ) pr.show_name = parse_bool( val );
+ else if ( key == "esp.projectile.show_timer_bar" ) pr.show_timer_bar = parse_bool( val );
+ else if ( key == "esp.projectile.show_inferno_bounds" ) pr.show_inferno_bounds = parse_bool( val );
+ else if ( key == "esp.projectile.show_smoke_voxels" ) pr.show_smoke_voxels = parse_bool( val );
+ else if ( key == "esp.projectile.color_he" ) pr.color_he = parse_color( val );
+ else if ( key == "esp.projectile.color_flash" ) pr.color_flash = parse_color( val );
+ else if ( key == "esp.projectile.color_smoke" ) pr.color_smoke = parse_color( val );
+ else if ( key == "esp.projectile.color_molotov" ) pr.color_molotov = parse_color( val );
+ else if ( key == "esp.projectile.color_decoy" ) pr.color_decoy = parse_color( val );
+ }
+
+ void config_system::load_misc( const std::string& key, const std::string& val )
+ {
+ auto& m = settings::g_misc.m_grenades;
+ if ( key == "misc.grenades.enabled" ) m.enabled = parse_bool( val );
+ else if ( key == "misc.grenades.line_color" ) m.line_color = parse_color( val );
+ else if ( key == "misc.grenades.line_thickness" ) m.line_thickness = parse_float( val );
+ else if ( key == "misc.grenades.line_gradient" ) m.line_gradient = parse_bool( val );
+ else if ( key == "misc.grenades.show_bounces" ) m.show_bounces = parse_bool( val );
+ else if ( key == "misc.grenades.bounce_color" ) m.bounce_color = parse_color( val );
+ else if ( key == "misc.grenades.bounce_size" ) m.bounce_size = parse_float( val );
+ else if ( key == "misc.grenades.detonate_color" ) m.detonate_color = parse_color( val );
+ else if ( key == "misc.grenades.detonate_size" ) m.detonate_size = parse_float( val );
+ else if ( key == "misc.grenades.per_type_colors" ) m.per_type_colors = parse_bool( val );
+ else if ( key == "misc.grenades.color_he" ) m.color_he = parse_color( val );
+ else if ( key == "misc.grenades.color_flash" ) m.color_flash = parse_color( val );
+ else if ( key == "misc.grenades.color_smoke" ) m.color_smoke = parse_color( val );
+ else if ( key == "misc.grenades.color_molotov" ) m.color_molotov = parse_color( val );
+ else if ( key == "misc.grenades.color_decoy" ) m.color_decoy = parse_color( val );
+ else if ( key == "misc.grenades.local_only" ) m.local_only = parse_bool( val );
+ else if ( key == "misc.grenades.fade_duration" ) m.fade_duration = parse_float( val );
+
+ auto& nh = settings::g_misc.m_nade_helper;
+ if ( key == "misc.nade_helper.enabled" ) nh.enabled = parse_bool( val );
+ else if ( key == "misc.nade_helper.show_name" ) nh.show_name = parse_bool( val );
+ else if ( key == "misc.nade_helper.show_type" ) nh.show_type = parse_bool( val );
+ else if ( key == "misc.nade_helper.stand_pos_color" ) nh.stand_pos_color = parse_color( val );
+ else if ( key == "misc.nade_helper.aim_pos_color" ) nh.aim_pos_color = parse_color( val );
+ else if ( key == "misc.nade_helper.text_color" ) nh.text_color = parse_color( val );
+ else if ( key == "misc.nade_helper.stand_radius" ) nh.stand_radius = parse_float( val );
+ else if ( key == "misc.nade_helper.aim_dot_size" ) nh.aim_dot_size = parse_float( val );
+
+ auto& mm = settings::g_misc.m_main;
+ if ( key == "misc.main.spectator_list" ) mm.spectator_list = parse_bool( val );
+ else if ( key == "misc.main.spectator_list_color" ) mm.spectator_list_color = parse_color( val );
+ else if ( key == "misc.main.bomb_timer" ) mm.bomb_timer = parse_bool( val );
+ else if ( key == "misc.main.bomb_timer_color" ) mm.bomb_timer_color = parse_color( val );
+ else if ( key == "misc.main.hitsound" ) mm.hitsound = parse_int( val );
+
+ auto& fc = settings::g_misc.m_fov_changer;
+ if ( key == "misc.fov_changer.enabled" ) fc.enabled = parse_bool( val );
+ else if ( key == "misc.fov_changer.fov" ) fc.fov = parse_int( val );
+ else if ( key == "misc.fov_changer.disable_when_scoped" ) fc.disable_when_scoped = parse_bool( val );
+ }
+
+ void config_system::load_movement( const std::string& key, const std::string& val )
+ {
+ auto& mv = settings::g_movement;
+ if ( key == "movement.bhop.enabled" ) mv.bhop.enabled = parse_bool( val );
+ else if ( key == "movement.quickstop.enabled" ) mv.quickstop.enabled = parse_bool( val );
+ else if ( key == "movement.quickstop.strength" ) mv.quickstop.strength = parse_float( val );
+ }
+
+ void config_system::load_skinchanger( const std::string& key, const std::string& val )
+ {
+ if ( key == "skinchanger.enabled" )
+ {
+ settings::g_skinchanger.enabled = parse_bool( val );
+ return;
+ }
+ if ( key == "skinchanger.music_kit" )
+ {
+ settings::g_skinchanger.music_kit = parse_int( val );
+ return;
+ }
+
+ if ( key.starts_with( "skinchanger.weapon_" ) )
+ {
+ const auto dot = key.find( '.', 19 );
+ if ( dot == std::string::npos ) return;
+
+ const int idx = std::stoi( key.substr( 19, dot - 19 ) );
+ const std::string subkey = key.substr( dot + 1 );
+
+ std::unique_lock lock( settings::g_skinchanger.mutex );
+ auto& cfg = settings::g_skinchanger.weapon_skins[ idx ];
+
+ if ( subkey == "paint_kit" ) cfg.paint_kit = parse_int( val );
+ else if ( subkey == "seed" ) cfg.seed = parse_int( val );
+ else if ( subkey == "stat_trak" ) cfg.stat_trak = parse_int( val );
+ else if ( subkey == "wear" ) cfg.wear = parse_float( val );
+ else if ( subkey == "uses_old_model" ) cfg.uses_old_model = parse_bool( val );
+ }
+ }
+
+ void config_system::remove( std::string_view name )
+ {
+ if ( name.empty( ) ) return;
+
+ fs::path file = this->m_path / ( std::string( name ) + ".cfg" );
+ if ( fs::exists( file ) )
+ {
+ fs::remove( file );
+ }
+ this->refresh_list( );
+ }
diff --git a/catalyst/project/core/config/config.hpp b/catalyst/project/core/config/config.hpp
new file mode 100644
index 0000000..3cd96ea
--- /dev/null
+++ b/catalyst/project/core/config/config.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+class config_system {
+public:
+ void save( std::string_view name );
+ void load( std::string_view name );
+ void remove( std::string_view name );
+
+ void refresh_list( );
+
+ [[nodiscard]] const std::vector& get_configs( ) const { return this->m_configs; }
+ [[nodiscard]] const std::filesystem::path& get_path( ) const { return this->m_path; }
+
+private:
+ void load_combat( const std::string& key, const std::string& val );
+ void load_esp_player( const std::string& key, const std::string& val );
+ void load_esp_item( const std::string& key, const std::string& val );
+ void load_esp_projectile( const std::string& key, const std::string& val );
+ void load_esp_radar(const std::string& key, const std::string& val);
+ void load_misc( const std::string& key, const std::string& val );
+ void load_movement( const std::string& key, const std::string& val );
+ void load_skinchanger( const std::string& key, const std::string& val );
+
+private:
+ std::vector m_configs{};
+ std::filesystem::path m_path{};
+
+public:
+ bool initialized{ false };
+ void init( );
+};
+
+namespace g {
+ inline config_system config{};
+}
diff --git a/catalyst/project/core/features/features.hpp b/catalyst/project/core/features/features.hpp
index 4dc6a5c..ebc7b30 100644
--- a/catalyst/project/core/features/features.hpp
+++ b/catalyst/project/core/features/features.hpp
@@ -1,4 +1,7 @@
-#pragma once
+#pragma once
+#include
+#include
+#include
namespace features {
@@ -16,7 +19,8 @@ namespace features {
const systems::collector::player* player{};
systems::bones::data bones{};
math::vector3 aim_point{};
- int hitbox{ -1 };
+ int bone{ -1 };
+ math::vector3 offset{};
int hitgroup{ -1 };
float damage{};
float fov{};
@@ -24,14 +28,15 @@ namespace features {
};
[[nodiscard]] target select_target( const math::vector3& eye_pos, const math::vector3& view_angles, const std::vector& players, const settings::combat::group_config& cfg ) const;
- [[nodiscard]] math::vector3 get_aim_point( const math::vector3& eye_pos, const systems::collector::player& player, const systems::bones::data& bones, const settings::combat::group_config& cfg, float& out_damage, int& out_hitbox, bool& out_penetrated ) const;
+ [[nodiscard]] math::vector3 get_aim_point( const math::vector3& eye_pos, const systems::collector::player& player, const systems::bones::data& bones, const settings::combat::group_config& cfg, float& out_damage, int& out_bone, math::vector3& out_offset, int& out_hitgroup, bool& out_penetrated ) const;
[[nodiscard]] float get_fov( const math::vector3& view_angles, const math::vector3& eye_pos, const math::vector3& target_pos ) const;
[[nodiscard]] float get_fov_radius( const math::vector3& eye_pos, const math::vector3& view_angles, float fov_degrees ) const;
- void draw_penetration_crosshair( zdraw::draw_list& draw_list, const math::vector3& eye_pos, const math::vector3& view_angles, const settings::combat::group_config& cfg );
- void draw_fov( zdraw::draw_list& draw_list, const math::vector3& eye_pos, const math::vector3& view_angles, const settings::combat::aimbot& cfg );
- void aimbot( const math::vector3& eye_pos, const math::vector3& view_angles, const target& tgt, const settings::combat::aimbot& cfg );
+ void draw_penetration_crosshair(zdraw::draw_list& draw_list, const math::vector3& eye_pos, const math::vector3& view_angles, const settings::combat::group_config& cfg);
+
+ void draw_fov( zdraw::draw_list& draw_list, const math::vector3& eye_pos, const math::vector3& view_angles, const settings::combat::aimbot_settings& cfg );
+ void aimbot(const math::vector3& eye_pos, const math::vector3& view_angles, const target& tgt, const settings::combat::aimbot_settings& cfg);
struct trigger_result
{
@@ -41,13 +46,15 @@ namespace features {
int hitgroup{ -1 };
float damage{};
bool penetrated{};
+ float sim_time{ 0.0f };
+ math::vector3 smoothed_offset{};
};
- [[nodiscard]] trigger_result trace_crosshair( const math::vector3& eye_pos, const math::vector3& view_angles, const std::vector& players, const settings::combat::triggerbot& cfg ) const;
- void triggerbot( const math::vector3& eye_pos, const math::vector3& view_angles, const std::vector& players, const settings::combat::triggerbot& cfg );
+ [[nodiscard]] trigger_result trace_crosshair( const math::vector3& eye_pos, const math::vector3& view_angles, const std::vector& players, const settings::combat::triggerbot_settings& cfg ) const;
+ void triggerbot(const math::vector3& eye_pos, const math::vector3& view_angles, const math::vector3& camera_angles, const std::vector& players, const settings::combat::triggerbot_settings& cfg);
void apply_autostop( );
- void release_autostop( );
+ void release_autostop( bool force = false );
animation::spring m_fov_alpha{};
random::valve_rng m_rng{};
@@ -59,10 +66,21 @@ namespace features {
bool m_trigger_waiting{ false };
bool m_trigger_held{ false };
float m_trigger_release_time{ 0.0f };
-
+ math::vector3 m_last_punch{};
bool m_autostop_active{ false };
std::chrono::steady_clock::time_point m_autostop_start{};
std::vector m_autostop_keys{};
+ std::vector m_autostop_inhibited_keys{};
+ std::uintptr_t s_magnet_pawn = 0;
+ float m_last_trigger_damage{ 0.0f };
+ float m_min_trigger_damage{ 0.0f };
+
+ struct prediction_data
+ {
+ math::vector3 smoothed_offset{};
+ math::vector3 last_velocity{};
+ };
+ mutable std::unordered_map m_prediction_history;
};
class shared
@@ -75,6 +93,7 @@ namespace features {
std::uint32_t weapon_type;
std::uint16_t item_def_idx;
int num_bullets;
+ float accuracy_penalty;
float inaccuracy;
float spread;
float recoil_index;
@@ -111,7 +130,7 @@ namespace features {
void prepare( std::uintptr_t weapon_vdata, std::uintptr_t weapon );
[[nodiscard]] bool run( const math::vector3& start, const math::vector3& end, const systems::collector::player& target, const systems::bones::data& bones, result& out ) const;
- [[nodiscard]] bool can( const math::vector3& start, const math::vector3& direction, float& out_damage ) const;
+ [[nodiscard]] bool can(const math::vector3& start, const math::vector3& direction, float& out_damage) const;
[[nodiscard]] float get_max_damage( int hitgroup, int target_armor, bool has_helmet, int target_team ) const;
[[nodiscard]] const weapon_data& get_weapon_data( ) const { return this->m_weapon_data; }
@@ -124,15 +143,18 @@ namespace features {
[[nodiscard]] const context& ctx( ) const { return this->m_ctx; }
[[nodiscard]] const penetration& pen( ) const { return this->m_pen; }
- [[nodiscard]] float calculate_hitchance( const math::vector3& eye_pos, const math::vector3& aim_angle, const systems::collector::player& target, const systems::bones::data& bones ) const;
+ [[nodiscard]] float calculate_hitchance( const math::vector3& eye_pos, const math::vector3& aim_angle, const systems::collector::player& target, const systems::bones::data& bones, const math::vector3& offset = {} ) const;
[[nodiscard]] std::uint32_t get_spread_seed( const math::vector3& angles, int tick ) const;
[[nodiscard]] math::vector2 calculate_spread( int seed, float accuracy, float spread, float recoil_index, int item_def_idx, int num_bullets ) const;
[[nodiscard]] math::vector3 extrapolate_stop( const math::vector3& pos ) const;
[[nodiscard]] bool is_sniper_accurate( ) const;
[[nodiscard]] float get_prediction_time( ) const;
+
+ private:
[[nodiscard]] float get_spread( std::uintptr_t weapon_vdata ) const;
- [[nodiscard]] float get_inaccuracy( std::uintptr_t pawn, std::uintptr_t weapon, std::uintptr_t weapon_vdata, const math::vector3& eye_angles ) const;
- [[nodiscard]] bool ray_hits_capsule( const math::vector3& ray_origin, const math::vector3& ray_dir, const math::vector3& capsule_start, const math::vector3& capsule_end, float radius ) const;
+ float get_base_inaccuracy(std::uintptr_t weapon, std::uintptr_t weapon_vdata, std::uintptr_t pawn) const;
+ float get_inaccuracy(std::uintptr_t pawn, std::uintptr_t weapon, std::uintptr_t weapon_vdata, const math::vector3& eye_angles, float* out_move_inaccuracy, float* out_air_inaccuracy) const;
+ //[[nodiscard]] float get_inaccuracy(std::uintptr_t pawn, std::uintptr_t weapon, std::uintptr_t weapon_vdata, const math::vector3& eye_angles) const;
context m_ctx{};
penetration m_pen{};
@@ -168,6 +190,8 @@ namespace features {
void add_name( zdraw::draw_list& draw_list, const systems::bounds::data& bounds, const systems::collector::player& player, const settings::esp::player::name& cfg, draw_offsets& offsets );
void add_weapon( zdraw::draw_list& draw_list, const systems::bounds::data& bounds, const systems::collector::player& player, const settings::esp::player::weapon& cfg, draw_offsets& offsets );
void add_flags( zdraw::draw_list& draw_list, const systems::bounds::data& bounds, const systems::collector::player& player, const settings::esp::player::info_flags& cfg, draw_offsets& offsets );
+ void add_oof_arrow( zdraw::draw_list& draw_list, const systems::collector::player& player, const settings::esp::player::oof_arrow& cfg );
+ void draw_movement_trails( zdraw::draw_list& draw_list );
[[nodiscard]] static std::string get_weapon_icon( const std::string& weapon_name );
@@ -176,11 +200,32 @@ namespace features {
animation::spring health{};
animation::spring ammo{};
bool initialized{ false };
- float last_damage_time{ 0.0f };
+ float last_hit_time{ 0.0f };
int last_health{ 100 };
};
+ struct trail_point
+ {
+ math::vector3 pos{};
+ float time{};
+ };
+
+ struct trail_data
+ {
+ std::deque path{};
+ };
+
+
std::unordered_map m_animations{};
+ std::unordered_map m_trails{};
+
+ void add_capsule( zdraw::draw_list& draw_list, const math::vector3& start, const math::vector3& end, float radius, const math::quaternion& rotation, const math::vector3& origin, const zdraw::rgba& color, int segments_max, bool red_only, float fill_line );
+ void draw_capsule_outline( zdraw::draw_list& draw_list, const math::vector3& top, const math::vector3& bottom, const math::vector3& axis, const math::vector3& u, const math::vector3& v, float radius, const zdraw::rgba& color, int segments, bool red_only, float fill_line );
+ void create_circle( const math::vector3& center, const math::vector3& u, const math::vector3& v, float radius, std::vector& out, int segments );
+ void precompute_sincos( int segments );
+
+ std::vector m_sin_cache{};
+ std::vector m_cos_cache{};
};
class item
@@ -209,16 +254,37 @@ namespace features {
private:
void draw_timer( zdraw::draw_list& draw_list, const math::vector2& screen, float& y_offset, float remaining, float frac, const settings::esp::projectile& cfg ) const;
void draw_inferno_bounds( zdraw::draw_list& draw_list, const systems::collector::projectile& proj, const settings::esp::projectile& cfg ) const;
-
+ void draw_smoke_voxels( zdraw::draw_list& draw_list, const settings::esp::projectile& cfg ) const;
[[nodiscard]] zdraw::rgba get_color( systems::collector::projectile_subtype type, const settings::esp::projectile& cfg ) const;
[[nodiscard]] std::string get_icon( systems::collector::projectile_subtype type ) const;
[[nodiscard]] std::string get_name( systems::collector::projectile_subtype type ) const;
[[nodiscard]] zdraw::rgba lerp_color( const zdraw::rgba& a, const zdraw::rgba& b, float t ) const;
};
+ class footsteps
+ {
+ public:
+ void on_render( zdraw::draw_list& draw_list );
+ void tick( );
+
+ private:
+ struct ring
+ {
+ math::vector3 world_pos{};
+ float max_radius{};
+ zdraw::rgba color{};
+ float start_time{};
+ };
+
+ std::vector m_rings{};
+ std::unordered_map m_prev_on_ground{};
+ std::unordered_map m_last_step_time{};
+ };
+
inline player g_player{};
inline item g_item{};
inline projectile g_projectile{};
+ inline footsteps g_footsteps{};
} // namespace esp
@@ -259,12 +325,13 @@ namespace features {
void update_in_flight( );
[[nodiscard]] std::uintptr_t hash_from_projectile( systems::collector::projectile_subtype type ) const;
+ [[nodiscard]] zdraw::rgba color_for_type( std::uintptr_t weapon_hash ) const;
void simulate( const math::vector3& start, const math::vector3& velocity, trajectory& out );
void step_simulation( math::vector3& pos, math::vector3& vel, systems::bvh::trace_result& trace );
void resolve_collision( const systems::bvh::trace_result& trace, math::vector3& pos, math::vector3& vel );
[[nodiscard]] bool should_detonate( const math::vector3& vel, int tick ) const;
- void render_trajectory( zdraw::draw_list& draw_list, const trajectory& traj, float alpha ) const;
+ void render_trajectory( zdraw::draw_list& draw_list, const trajectory& traj, float alpha, std::uintptr_t weapon_hash ) const;
std::uintptr_t m_weapon_vdata{};
std::uintptr_t m_weapon_hash{};
@@ -279,6 +346,7 @@ namespace features {
float m_sv_gravity{};
float m_molotov_max_slope_z{};
+ static constexpr auto tick_interval{ 1.0f / 64.0f };
static constexpr auto gravity_scale{ 0.4f };
static constexpr auto elasticity{ 0.45f };
static constexpr auto max_ticks{ 1024 };
@@ -287,15 +355,164 @@ namespace features {
static constexpr auto missing_grace{ 0.1f };
};
+ class misc_features
+ {
+ public:
+ void on_render( zdraw::draw_list& draw_list );
+ void tick( );
+ void tick_write( );
+
+ private:
+ void draw_spectators( zdraw::draw_list& draw_list );
+ void draw_bomb_timer( zdraw::draw_list& draw_list );
+ void hitsounds( );
+ void draw_hit_markers( zdraw::draw_list& draw_list );
+ void draw_damage_indicators( zdraw::draw_list& draw_list );
+ void draw_watermark( zdraw::draw_list& draw_list );
+ void fov_changer( );
+
+ struct hit_marker_t
+ {
+ std::chrono::steady_clock::time_point time;
+ float max_time;
+ };
+
+ struct damage_indicator_t
+ {
+ math::vector3 pos;
+ float damage;
+ bool is_headshot;
+ std::chrono::steady_clock::time_point time;
+ float max_time;
+ };
+
+ int m_old_hits{ 0 };
+ int m_old_damage{ 0 };
+ std::vector m_hit_markers{};
+ std::vector m_damage_indicators{};
+
+ struct health_history_t
+ {
+ int health{ -1 };
+ math::vector3 origin{};
+ };
+ std::unordered_map m_health_history{};
+ mutable std::shared_mutex m_mutex{};
+ };
+
class impacts
{
public:
+ void on_render( zdraw::draw_list& draw_list );
+ void tick( );
+
+ private:
+ struct tracer_t
+ {
+ math::vector3 start;
+ math::vector3 end;
+ float time;
+ float max_time;
+ };
+
+ std::vector m_tracers{};
+ int m_old_shots{ 0 };
+ };
+
+ struct nade_data
+ {
+ std::string name;
+ math::vector3 pos;
+ math::vector3 target_pos;
+ int throw_type; // 0: stand, 1: jump, 2: walk, 3: run, 4: crouch, 5: crouch jump
+ int nade_type; // 0: HE, 1: Flash, 2: Smoke, 3: Molly, 4: Incendiary, 5: Decoy
+ };
+
+ class nade_helper
+ {
+ public:
+ void on_render( zdraw::draw_list& draw_list );
+ void tick( );
+ void load_nades( const std::string& map_name );
+ void save_nades( const std::string& map_name );
+
+ [[nodiscard]] std::vector& get_nades( ) { return m_nades; }
+
+ private:
+ std::vector m_nades{};
+ std::string m_current_map{};
};
inline grenades g_grenades{};
inline impacts g_impacts{};
+ inline misc_features g_misc{};
+ inline nade_helper g_nade_helper{};
} // namespace misc
-} // namespace features
\ No newline at end of file
+ namespace movement {
+
+ class movement_features
+ {
+ public:
+ void tick( );
+ };
+
+ inline movement_features g_movement{};
+
+ } // namespace movement
+
+
+
+ namespace skinchanger {
+
+ enum weapons_enum : std::uint16_t
+ {
+ none = 0,
+ deagle = 1, elite = 2, fiveseven = 3, glock = 4, ak47 = 7, aug = 8, awp = 9, famas = 10, g3sg1 = 11, m249 = 14, mac10 = 17, p90 = 19, ump45 = 24, xm1014 = 25, bizon = 26, mag7 = 27, negev = 28, sawedoff = 29, tec9 = 30, zeus = 31, p2000 = 32, mp7 = 33, mp9 = 34, nova = 35, p250 = 36, scar20 = 38, sg556 = 39, ssg08 = 40, ct_knife = 42, m4a4 = 16, usps = 61, m4a1s = 60, cz75 = 63, revolver = 64, t_knife = 59, galil = 13, mp5sd = 23
+ };
+
+ struct skin_info
+ {
+ int paint_kit;
+ bool uses_old_model;
+ std::string name;
+ weapons_enum weapon_type;
+ };
+
+ struct music_kit_info
+ {
+ int id;
+ std::string name;
+ };
+
+ class skin_db
+ {
+ public:
+ void initialize( );
+ std::vector get_weapon_skins( weapons_enum type = weapons_enum::none );
+ std::vector get_music_kits( );
+
+ private:
+ std::vector m_skins{};
+ };
+
+ class skinchanger
+ {
+ public:
+ std::uintptr_t get_hud_arms(std::uintptr_t local_player);
+ std::uintptr_t get_hud_weapon(std::uintptr_t local_player, std::uintptr_t weapon);
+ void tick( );
+ bool m_force_update{ false };
+ uint64_t regen_skins{ 0 };
+ private:
+ std::vector get_weapons( std::uintptr_t local_player );
+ };
+
+ inline skin_db g_skindb{};
+ inline skinchanger g_skinchanger{};
+
+ } // namespace skinchanger
+
+} // namespace features
diff --git a/catalyst/project/core/features/impl/combat/legit.cpp b/catalyst/project/core/features/impl/combat/legit.cpp
index 63fb7e7..07ce29f 100644
--- a/catalyst/project/core/features/impl/combat/legit.cpp
+++ b/catalyst/project/core/features/impl/combat/legit.cpp
@@ -1,127 +1,478 @@
-#include
+#include
+#include
+
+namespace {
+ bool ray_hits_capsule( const math::vector3& ray_origin, const math::vector3& ray_dir, const math::vector3& capsule_start, const math::vector3& capsule_end, float radius )
+ {
+ const auto capsule_vec = capsule_end - capsule_start;
+ const auto capsule_length = capsule_vec.length( );
+
+ if ( capsule_length < 0.001f )
+ {
+ const auto to_center = capsule_start - ray_origin;
+ const auto projection = to_center.dot( ray_dir );
+
+ if ( projection < 0.0f )
+ {
+ return false;
+ }
+
+ const auto closest = ray_origin + ray_dir * projection;
+ return ( closest - capsule_start ).length_sqr( ) <= radius * radius;
+ }
+
+ const auto capsule_dir = capsule_vec / capsule_length;
+ const auto w = ray_origin - capsule_start;
+
+ const auto a = ray_dir.dot( ray_dir );
+ const auto b = ray_dir.dot( capsule_dir );
+ const auto c = capsule_dir.dot( capsule_dir );
+ const auto d = ray_dir.dot( w );
+ const auto e = capsule_dir.dot( w );
+
+ const auto denom = a * c - b * b;
+
+ float s, t;
+
+ if ( std::abs( denom ) < 0.0001f )
+ {
+ s = 0.0f;
+ t = ( b > c ? d / b : e / c );
+ }
+ else
+ {
+ s = ( b * e - c * d ) / denom;
+ t = ( a * e - b * d ) / denom;
+ }
+
+ t = std::clamp( t, 0.0f, capsule_length );
+ if ( s < 0.0f )
+ {
+ return false;
+ }
+
+ const auto point_on_capsule = capsule_start + capsule_dir * t;
+ const auto point_on_ray = ray_origin + ray_dir * s;
+
+ return ( point_on_ray - point_on_capsule ).length_sqr( ) <= radius * radius;
+ }
+}
namespace features::combat {
void legit::on_render( zdraw::draw_list& draw_list )
{
- const auto eye_pos = systems::g_view.origin( );
- const auto view_angles = systems::g_view.angles( );
+ const auto eye_pos = systems::g_view.origin();
+ const auto view_angles = systems::g_view.angles();
- const auto& ctx = g_shared.ctx( );
- const auto& cfg = settings::g_combat.get( ctx.weapon_type );
-
- if ( !ctx.valid )
+ const auto& ctx = g_shared.ctx();
+ if (!ctx.valid)
{
return;
}
- const auto valid_weapon = cstypes::is_weapon_valid( ctx.weapon_type );
+ const auto valid_weapon = cstypes::is_weapon_valid(ctx.weapon_type);
+ const auto& cfg = settings::g_combat.get(ctx.weapon_type);
+
+ this->m_fov_alpha.set_target(valid_weapon && cfg.aimbot.draw_fov && cfg.aimbot.enabled ? 1.0f : 0.0f);
+ this->m_fov_alpha.update();
- if ( valid_weapon && cfg.other.penetration_crosshair )
+ if (this->m_fov_alpha.value() > 0.01f)
{
- this->draw_penetration_crosshair( draw_list, eye_pos, view_angles, cfg );
+ this->draw_fov(draw_list, eye_pos, view_angles, cfg.aimbot);
}
- this->m_fov_alpha.set_target( valid_weapon && cfg.aimbot.draw_fov && cfg.aimbot.enabled ? 1.0f : 0.0f );
- this->m_fov_alpha.update( );
+ if (cfg.aimbot.autowall_info)
+ {
+ const auto [w, h] = zdraw::get_display_size();
+
+ struct InfoLine
+ {
+ std::string text;
+ bool enabled = true;
+ };
+
+ std::vector lines;
+
+ if (cfg.triggerbot.autowall)
+ {
+ lines.emplace_back(std::format("awall - min: {:.1f}", cfg.triggerbot.min_damage));
+ }
+
+ lines.emplace_back(std::format("fov: {:.1f} hc: {:.1f}",
+ static_cast(cfg.aimbot.fov),
+ cfg.triggerbot.hitchance));
+
+ if (lines.empty())
+ return;
+
+ zdraw::push_font(g::render.fonts().pretzel_24);
+
+ float max_width = 0.0f;
+ float total_height = 0.0f;
+ const float line_spacing = 2.0f;
+
+ std::vector> text_sizes;
+
+ for (const auto& line : lines)
+ {
+ if (!line.enabled || line.text.empty())
+ continue;
+
+ auto [tw, th] = zdraw::measure_text(line.text.c_str());
+ text_sizes.emplace_back(tw, th);
+
+ max_width = std::max(max_width, tw);
+ total_height += th + line_spacing;
+ }
+
+ if (text_sizes.empty())
+ {
+ zdraw::pop_font();
+ return;
+ }
+
+ total_height -= line_spacing;
+
+ const float x = w * 0.5f;
+ const float y = (h * 0.5f) + 35.0f;
+
+ const float padding_x = 4.0f;
+ const float padding_y = 2.0f;
+
+ draw_list.add_rect_filled(
+ x - (max_width * 0.5f) - padding_x,
+ y - padding_y,
+ max_width + padding_x * 2.0f,
+ total_height + padding_y * 2.0f,
+ zdraw::rgba{ 12, 12, 12, 160 }
+ );
+
+ draw_list.add_rect(
+ x - (max_width * 0.5f) - padding_x,
+ y - padding_y,
+ max_width + padding_x * 2.0f,
+ total_height + padding_y * 2.0f,
+ zdraw::rgba{ 45, 45, 45, 200 }
+ );
+
+ float current_y = y;
+
+ for (size_t i = 0; i < lines.size(); ++i)
+ {
+ if (!lines[i].enabled || lines[i].text.empty())
+ continue;
+
+ const auto [tw, th] = text_sizes[i];
+
+ draw_list.add_text(
+ x - (tw * 0.5f),
+ current_y,
+ lines[i].text.c_str(),
+ zdraw::get_font(),
+ cfg.aimbot.autowall_info_color,
+ zdraw::text_style::outlined
+ );
+
+ current_y += th + line_spacing;
+ }
+
+ zdraw::pop_font();
+ }
- if ( this->m_fov_alpha.value( ) <= 0.01f )
+ if (valid_weapon && cfg.other.penetration_crosshair)
{
- return;
+ this->draw_penetration_crosshair(draw_list, systems::g_local.eye_position(), view_angles, cfg);
}
+
+ // holy slop never let me make visuals ever again
+ if (cfg.triggerbot.enabled && cfg.triggerbot.predictive && cfg.triggerbot.predictive_visualize)
+ {
+ const auto players = systems::g_collector.players();
+ const auto prediction_time = static_cast(cfg.triggerbot.predictive_ms) * 0.001f;
+
+ for (const auto& player : players)
+ {
+ if (!systems::g_local.is_enemy(player.team) || !player.alive)
+ continue;
- this->draw_fov( draw_list, eye_pos, view_angles, cfg.aimbot );
+ const auto bones = systems::g_bones.get(player.bone_cache);
+ if (!bones.is_valid())
+ continue;
+
+ const auto prediction_offset = (player.velocity * prediction_time) + (player.acceleration * 0.5f * prediction_time * prediction_time);
+
+ auto& history = this->m_prediction_history[player.pawn];
+ const auto dt = zdraw::get_delta_time();
+
+ if (history.smoothed_offset.length_sqr() == 0.0f)
+ {
+ history.smoothed_offset = prediction_offset;
+ }
+ else
+ {
+ history.smoothed_offset.x += (prediction_offset.x - history.smoothed_offset.x) * 12.0f * dt;
+ history.smoothed_offset.y += (prediction_offset.y - history.smoothed_offset.y) * 12.0f * dt;
+ history.smoothed_offset.z += (prediction_offset.z - history.smoothed_offset.z) * 12.0f * dt;
+ }
+
+ const auto color = zdraw::rgba{ 50, 255, 50, 150 };
+
+ static constexpr std::pair connections[] = {
+ { 6, 5 }, { 5, 4 }, { 4, 3 }, { 3, 2 }, { 2, 8 }, { 8, 9 }, { 9, 10 }, { 10, 11 },
+ { 3, 13 }, { 13, 14 }, { 14, 15 }, { 15, 16 }, { 4, 18 }, { 18, 19 }, { 19, 20 },
+ { 4, 21 }, { 21, 22 }, { 22, 23 }
+ };
+
+ for (const auto& [a, b] : connections)
+ {
+ const auto pos_a = systems::g_view.project(bones.get_position(a) + history.smoothed_offset);
+ const auto pos_b = systems::g_view.project(bones.get_position(b) + history.smoothed_offset);
+
+ if (systems::g_view.projection_valid(pos_a) && systems::g_view.projection_valid(pos_b))
+ {
+ draw_list.add_line(pos_a.x, pos_a.y, pos_b.x, pos_b.y, color, 1.0f);
+ }
+ }
+ }
+ }
+
+ if (cfg.triggerbot.enabled && cfg.triggerbot.show_spread)
+ {
+ const auto pawn = systems::g_local.pawn();
+ if (pawn && ctx.weapon && ctx.weapon_vdata)
+ {
+ const auto [display_w, display_h] = zdraw::get_display_size();
+ const auto center_x = static_cast(display_w) * 0.5f;
+ const auto center_y = static_cast(display_h) * 0.5f;
+
+ const auto global_vars = g::memory.read( g::offsets.global_vars );
+ const auto render_tick = g::memory.read(systems::g_local.controller() + SCHEMA("CBasePlayerController", "m_nTickBase"_hash));
+
+ const auto fov_rad = systems::g_view.fov() * ( std::numbers::pi_v / 180.0f );
+ const auto pixels_per_rad = static_cast(display_w) / ( 2.0f * std::tanf( fov_rad * 0.5f ) );
+
+ const auto inac_radius_px = ctx.inaccuracy * pixels_per_rad;
+ if ( inac_radius_px > 1.0f && inac_radius_px < center_x )
+ {
+ draw_list.add_circle( center_x, center_y, inac_radius_px, zdraw::rgba{ 200, 200, 220, 60 }, 64, 1.0f );
+ }
+
+ const auto spread_radius_px = ctx.spread * pixels_per_rad;
+ if ( spread_radius_px > 1.0f && spread_radius_px < center_x )
+ {
+ draw_list.add_circle( center_x, center_y, spread_radius_px, zdraw::rgba{ 180, 180, 200, 45 }, 64, 1.0f );
+ }
+
+ const auto aim_punch = g::memory.read( pawn + SCHEMA("C_CSPlayerPawn", "m_aimPunchAngle"_hash) );
+ const auto view = view_angles + aim_punch;
+
+ constexpr auto deg2rad = std::numbers::pi_v / 180.0f;
+ const auto sp = std::sinf( view.x * deg2rad );
+ const auto cp = std::cosf( view.x * deg2rad );
+ const auto sy = std::sinf( view.y * deg2rad );
+ const auto cy = std::cosf( view.y * deg2rad );
+
+ const math::vector3 forward{ cp * cy, cp * sy, -sp };
+ const math::vector3 right{ -sy, cy, 0.0f };
+ const math::vector3 up{ sp * cy, sp * sy, cp };
+
+ struct capsule_t
+ {
+ math::vector3 start;
+ math::vector3 end;
+ float radius;
+ };
+
+ std::vector all_capsules;
+ const auto players = systems::g_collector.players();
+
+ for ( const auto& player : players )
+ {
+ if ( !systems::g_local.is_enemy(player.team) || player.health <= 0 || player.invulnerable || player.hitboxes.count <= 0 )
+ continue;
+
+ const auto bone_data = systems::g_bones.get( player.bone_cache );
+ if ( !bone_data.is_valid( ) )
+ continue;
+
+ for ( const auto& hb : player.hitboxes )
+ {
+ if ( hb.index < 0 || hb.bone < 0 || hb.radius <= 0.0f )
+ continue;
+
+ const auto& bone = bone_data.bones[ hb.bone ];
+ const auto center_local = ( hb.mins + hb.maxs ) * 0.5f;
+ const auto center_world = bone.position + math::helpers::rotate_by_quat( bone.rotation, center_local );
+
+ const auto half_extent = ( hb.maxs - hb.mins ) * 0.5f;
+ const auto longest = std::max( { std::abs( half_extent.x ), std::abs( half_extent.y ), std::abs( half_extent.z ) } );
+
+ math::vector3 axis_local{};
+ if ( std::abs( half_extent.x ) >= std::abs( half_extent.y ) && std::abs( half_extent.x ) >= std::abs( half_extent.z ) )
+ axis_local = { longest, 0.0f, 0.0f };
+ else if ( std::abs( half_extent.y ) >= std::abs( half_extent.z ) )
+ axis_local = { 0.0f, longest, 0.0f };
+ else
+ axis_local = { 0.0f, 0.0f, longest };
+
+ const auto axis_world = math::helpers::rotate_by_quat( bone.rotation, axis_local );
+ const auto capsule_start = center_world - axis_world;
+ const auto capsule_end = center_world + axis_world;
+
+ all_capsules.push_back( { capsule_start, capsule_end, hb.radius * 0.9f } );
+ }
+ }
+
+ const auto eye_origin = eye_pos;
+
+ for ( int i = 0; i < 64; ++i )
+ {
+ const auto tick = render_tick - 1 + i;
+ const auto seed = g_shared.get_spread_seed( view_angles, tick );
+ const auto sv = g_shared.calculate_spread( seed + 1, ctx.inaccuracy, ctx.spread, ctx.recoil_index, ctx.item_def_idx, 0 );
+
+ const auto dot_x = center_x + ( sv.x * pixels_per_rad );
+ const auto dot_y = center_y + ( -sv.y * pixels_per_rad );
+
+ const auto dir = ( forward + right * -sv.x + up * sv.y ).normalized( );
+
+ auto would_hit{ false };
+
+ for ( const auto& cap : all_capsules )
+ {
+ if ( ray_hits_capsule( eye_origin, dir, cap.start, cap.end, cap.radius ) )
+ {
+ would_hit = true;
+ break;
+ }
+ }
+
+ const auto is_fire_tick = ( i < 2 );
+ const auto alpha_scale = is_fire_tick ? 1.0f : ( 1.0f - static_cast< float >( i ) / 64.0f );
+
+ if ( is_fire_tick )
+ {
+ const auto color = would_hit ? zdraw::rgba{ 180, 240, 180, 255 } : zdraw::rgba{ 240, 160, 170, 240 };
+ draw_list.add_circle_filled( dot_x, dot_y, 5, color, 8 );
+ }
+ else
+ {
+ const auto color = would_hit ? zdraw::rgba{ 160, 220, 190, static_cast< std::uint8_t >( 200.0f * alpha_scale ) } : zdraw::rgba{ 220, 160, 170, static_cast< std::uint8_t >( 140.0f * alpha_scale ) };
+ draw_list.add_circle_filled( dot_x, dot_y, 3, color, 6 );
+ }
+ }
+ }
+ }
}
- void legit::tick( )
+ void legit::tick()
{
- if ( !this->m_rng_seeded )
+ if (!this->m_rng_seeded)
{
- this->m_rng.seed( static_cast< int >( std::chrono::steady_clock::now( ).time_since_epoch( ).count( ) & 0x7fffffff ) );
+ this->m_rng.seed(static_cast(std::chrono::steady_clock::now().time_since_epoch().count() & 0x7fffffff));
this->m_rng_seeded = true;
}
- if ( this->m_trigger_held )
+ if (this->m_trigger_held)
{
- const auto& ctx = g_shared.ctx( );
- if ( !ctx.valid || ctx.current_time >= this->m_trigger_release_time )
+ const auto& ctx = g_shared.ctx();
+ if (!ctx.valid || ctx.current_time >= this->m_trigger_release_time)
{
- g::input.inject_mouse( 0, 0, input::left_up );
+ g::input.inject_mouse(0, 0, input::left_up);
this->m_trigger_held = false;
}
}
- const auto& ctx = g_shared.ctx( );
- if ( !ctx.valid )
+ const auto& ctx = g_shared.ctx();
+ if (!ctx.valid)
{
return;
}
- const auto valid_weapon = cstypes::is_weapon_valid( ctx.weapon_type );
- const auto& cfg = settings::g_combat.get( ctx.weapon_type );
+ const auto valid_weapon = cstypes::is_weapon_valid(ctx.weapon_type);
+ const auto& cfg = settings::g_combat.get(ctx.weapon_type);
- if ( !valid_weapon )
+ if (!valid_weapon)
{
return;
}
- const auto eye_pos = systems::g_view.origin( );
- const auto view_angles = systems::g_view.angles( );
- const auto players = systems::g_collector.players( );
+ const auto eye_pos = systems::g_local.eye_position();
+ const auto view_angles = g::memory.read(systems::g_local.pawn() + SCHEMA("C_BasePlayerPawn", "v_angle"_hash));
+ const auto camera_angles = systems::g_view.angles();
+
+ const auto players = systems::g_collector.players();
- if ( !ctx.is_reloading && ctx.weapon_ready )
+ if (!ctx.is_reloading && ctx.weapon_ready)
{
- if ( cfg.aimbot.enabled )
+ bool aimbot_active = false;
+ const auto pawn = systems::g_local.pawn();
+ const auto shots = pawn ? g::memory.read(pawn + SCHEMA("C_CSPlayerPawn", "m_iShotsFired"_hash)) : 0;
+
+ if (cfg.aimbot.enabled)
{
- const auto target = this->select_target( eye_pos, view_angles, players, cfg );
- if ( target.player )
+ const auto target = this->select_target(eye_pos, view_angles, players, cfg);
+ if (target.player)
{
- this->aimbot( eye_pos, view_angles, target, cfg.aimbot );
+ if (GetAsyncKeyState(cfg.aimbot.key) & 0x8000)
+ {
+ this->aimbot(eye_pos, camera_angles, target, cfg.aimbot);
+ aimbot_active = true;
+ }
}
}
- if ( cfg.triggerbot.enabled )
+ if (cfg.triggerbot.enabled)
{
- this->triggerbot( eye_pos, view_angles, players, cfg.triggerbot );
+ this->triggerbot(eye_pos, view_angles, camera_angles, players, cfg.triggerbot);
}
}
+ else
+ {
+ this->m_last_punch = {};
+ this->release_autostop(true);
+ this->s_magnet_pawn = 0;
+ }
}
- legit::target legit::select_target( const math::vector3& eye_pos, const math::vector3& view_angles, const std::vector& players, const settings::combat::group_config& cfg ) const
+ legit::target legit::select_target(const math::vector3& eye_pos, const math::vector3& view_angles, const std::vector& players, const settings::combat::group_config& cfg) const
{
target best{};
- best.fov = static_cast< float >( cfg.aimbot.fov );
+ best.fov = static_cast(cfg.aimbot.fov);
- for ( const auto& player : players )
+ for (const auto& player : players)
{
- if ( !systems::g_local.is_enemy( player.team ) )
+ if (!systems::g_local.is_enemy(player.team) || !player.alive)
{
continue;
}
- if ( player.invulnerable || player.hitboxes.count <= 0 )
+ if (player.invulnerable || player.hitboxes.count <= 0)
{
continue;
}
- const auto bones = systems::g_bones.get( player.bone_cache );
- if ( !bones.is_valid( ) )
+ const auto bones = systems::g_bones.get(player.bone_cache);
+ if (!bones.is_valid())
{
continue;
}
auto damage{ 0.0f };
- auto hitbox{ -1 };
+ auto bone{ -1 };
+ auto hitgroup{ -1 };
+ math::vector3 offset{};
auto penetrated{ false };
- const auto aim_point = this->get_aim_point( eye_pos, player, bones, cfg, damage, hitbox, penetrated );
- if ( hitbox < 0 )
+ const auto aim_point = this->get_aim_point(eye_pos, player, bones, cfg, damage, bone, offset, hitgroup, penetrated);
+ if (bone < 0)
{
continue;
}
- const auto fov = this->get_fov( view_angles, eye_pos, aim_point );
- if ( fov > best.fov )
+ const auto fov = this->get_fov(view_angles, eye_pos, aim_point);
+ if (fov > best.fov)
{
continue;
}
@@ -129,7 +480,9 @@ namespace features::combat {
best.player = &player;
best.bones = bones;
best.aim_point = aim_point;
- best.hitbox = hitbox;
+ best.bone = bone;
+ best.offset = offset;
+ best.hitgroup = hitgroup;
best.damage = damage;
best.fov = fov;
best.penetrated = penetrated;
@@ -138,88 +491,167 @@ namespace features::combat {
return best;
}
- math::vector3 legit::get_aim_point( const math::vector3& eye_pos, const systems::collector::player& player, const systems::bones::data& bones, const settings::combat::group_config& cfg, float& out_damage, int& out_hitbox, bool& out_penetrated ) const
+ math::vector3 legit::get_aim_point(const math::vector3& eye_pos, const systems::collector::player& player, const systems::bones::data& bones, const settings::combat::group_config& cfg, float& out_damage, int& out_bone, math::vector3& out_offset, int& out_hitgroup, bool& out_penetrated) const
{
- out_hitbox = -1;
+ out_bone = -1;
+
+ auto is_hg_enabled = [&](int hg) {
+ switch (hg) {
+ case 1: return cfg.aimbot.hitgroups.head;
+ case 2: return cfg.aimbot.hitgroups.chest;
+ case 3: return cfg.aimbot.hitgroups.stomach;
+ case 6: case 7: return cfg.aimbot.hitgroups.arms;
+ case 4: case 5: return cfg.aimbot.hitgroups.legs;
+ default: return false;
+ }
+ };
- for ( const auto& hb : player.hitboxes )
+ const auto view_angles = systems::g_view.angles();
+ float best_fov = static_cast(cfg.aimbot.fov);
+ math::vector3 best_point{};
+
+ for (const auto& hb : player.hitboxes)
{
- if ( hb.index < 0 || hb.bone < 0 )
+ if (hb.index < 0 || hb.bone < 0)
{
continue;
}
- if ( cfg.aimbot.head_only && hb.index > 1 )
+ const auto hitgroup = systems::g_hitboxes.hitgroup_from_hitbox(hb.index);
+ if (!is_hg_enabled(hitgroup))
{
continue;
}
- const auto pos = bones.get_position( hb.bone );
- const auto hitgroup = systems::g_hitboxes.hitgroup_from_hitbox( hb.index );
+ const auto bone_pos = bones.get_position(hb.bone);
+ const auto bone_rot = bones.get_rotation(hb.bone);
+
+ // Use the center of the hitbox segment (mins/maxs are usually local to the bone)
+ const auto local_center = (hb.mins + hb.maxs) * 0.5f;
+ const auto center = bone_pos + bone_rot.rotate_vector(local_center);
+
+ std::vector points{};
+ points.push_back(center);
- if ( !cfg.aimbot.visible_only )
+ if (cfg.aimbot.multipoint)
{
- out_damage = combat::g_shared.pen( ).get_max_damage( hitgroup, player.armor, player.has_helmet, player.team );
- out_hitbox = hb.index;
- out_penetrated = false;
- return pos;
- }
+ const auto scale = cfg.aimbot.multipoint_scale;
+ const auto radius = hb.radius * scale;
- const auto trace = systems::g_bvh.trace_ray( eye_pos, pos );
- const auto visible = !trace.hit || trace.fraction > 0.97f;
+ if (radius > 0.05f)
+ {
+ const auto right = bone_rot.rotate_vector({ 0.0f, 1.0f, 0.0f });
+ const auto up = bone_rot.rotate_vector({ 0.0f, 0.0f, 1.0f });
- if ( visible )
- {
- out_damage = combat::g_shared.pen( ).get_max_damage( hitgroup, player.armor, player.has_helmet, player.team );
- out_hitbox = hb.index;
- out_penetrated = false;
- return pos;
+ if (hitgroup == 1) // head
+ {
+ points.push_back(center + up * radius);
+ points.push_back(center - up * radius);
+ points.push_back(center + right * radius);
+ points.push_back(center - right * radius);
+
+ const auto diagonal = (right + up).normalized();
+ points.push_back(center + diagonal * radius);
+ points.push_back(center - diagonal * radius);
+ }
+ else
+ {
+ points.push_back(center + right * radius);
+ points.push_back(center - right * radius);
+ points.push_back(center + up * radius);
+ points.push_back(center - up * radius);
+ }
+ }
}
- if ( cfg.aimbot.autowall )
+ for (const auto& pos : points)
{
- shared::penetration::result pen_result{};
- if ( combat::g_shared.pen( ).run( eye_pos, pos, player, bones, pen_result ) )
+ if (cfg.aimbot.smoke_check && systems::g_voxels.line_goes_through_smoke(eye_pos, pos))
+ {
+ continue;
+ }
+
+ float current_damage = 0.0f;
+ bool current_penetrated = false;
+ bool valid = false;
+
+ if (!cfg.aimbot.visible_only)
+ {
+ current_damage = combat::g_shared.pen().get_max_damage(hitgroup, player.armor, player.has_helmet, player.team);
+ current_penetrated = false;
+ valid = true;
+ }
+ else
+ {
+ const auto trace = systems::g_bvh.trace_ray(eye_pos, pos);
+ auto visible = !trace.hit || trace.fraction > 0.97f;
+
+ if (visible)
+ {
+ current_damage = combat::g_shared.pen().get_max_damage(hitgroup, player.armor, player.has_helmet, player.team);
+ current_penetrated = false;
+ valid = true;
+ }
+ else if (cfg.aimbot.autowall)
+ {
+ shared::penetration::result pen_result{};
+ if (combat::g_shared.pen().run(eye_pos, pos, player, bones, pen_result))
+ {
+ if (pen_result.damage >= cfg.aimbot.min_damage)
+ {
+ current_damage = pen_result.damage;
+ current_penetrated = pen_result.penetrated;
+ valid = true;
+ }
+ }
+ }
+ }
+
+ if (valid)
{
- if ( pen_result.damage >= cfg.aimbot.min_damage )
+ const auto fov = this->get_fov(view_angles, eye_pos, pos);
+ if (fov < best_fov)
{
- out_damage = pen_result.damage;
- out_hitbox = pen_result.hitbox;
- out_penetrated = pen_result.penetrated;
- return pos;
+ best_fov = fov;
+ best_point = pos;
+ out_damage = current_damage;
+ out_bone = hb.bone;
+ out_offset = pos - bone_pos; // Store offset relative to the base bone position
+ out_hitgroup = hitgroup;
+ out_penetrated = current_penetrated;
}
}
}
}
- return {};
+ return best_point;
}
- float legit::get_fov( const math::vector3& view_angles, const math::vector3& eye_pos, const math::vector3& target_pos ) const
+ float legit::get_fov(const math::vector3& view_angles, const math::vector3& eye_pos, const math::vector3& target_pos) const
{
- return math::helpers::calculate_fov( view_angles, eye_pos, target_pos );
+ return math::helpers::calculate_fov(view_angles, eye_pos, target_pos);
}
- float legit::get_fov_radius( const math::vector3& eye_pos, const math::vector3& view_angles, float fov_degrees ) const
+ float legit::get_fov_radius(const math::vector3& eye_pos, const math::vector3& view_angles, float fov_degrees) const
{
- if ( fov_degrees <= 0.0f )
+ if (fov_degrees <= 0.0f)
{
return 0.0f;
}
math::vector3 forward{};
- view_angles.to_directions( &forward, nullptr, nullptr );
+ view_angles.to_directions(&forward, nullptr, nullptr);
auto offset_angles = view_angles;
offset_angles.x -= fov_degrees;
math::vector3 offset_forward{};
- offset_angles.to_directions( &offset_forward, nullptr, nullptr );
+ offset_angles.to_directions(&offset_forward, nullptr, nullptr);
- const auto center = systems::g_view.project( eye_pos + forward * 1000.0f );
- const auto edge = systems::g_view.project( eye_pos + offset_forward * 1000.0f );
+ const auto center = systems::g_view.project(eye_pos + forward * 1000.0f);
+ const auto edge = systems::g_view.project(eye_pos + offset_forward * 1000.0f);
- if ( !systems::g_view.projection_valid( center ) || !systems::g_view.projection_valid( edge ) )
+ if (!systems::g_view.projection_valid(center) || !systems::g_view.projection_valid(edge))
{
return 0.0f;
}
@@ -227,34 +659,34 @@ namespace features::combat {
const auto dx = edge.x - center.x;
const auto dy = edge.y - center.y;
- return std::sqrtf( dx * dx + dy * dy );
+ return std::sqrtf(dx * dx + dy * dy);
}
- void legit::draw_penetration_crosshair( zdraw::draw_list& draw_list, const math::vector3& eye_pos, const math::vector3& view_angles, const settings::combat::group_config& cfg )
+ void legit::draw_penetration_crosshair(zdraw::draw_list& draw_list, const math::vector3& eye_pos, const math::vector3& view_angles, const settings::combat::group_config& cfg)
{
math::vector3 forward{};
- view_angles.to_directions( &forward, nullptr, nullptr );
+ view_angles.to_directions(&forward, nullptr, nullptr);
- const auto first_hit = systems::g_bvh.trace_ray( eye_pos, eye_pos + forward * g_shared.pen( ).get_weapon_data( ).range );
- if ( !first_hit.hit )
+ const auto first_hit = systems::g_bvh.trace_ray(eye_pos, eye_pos + forward * g_shared.pen().get_weapon_data().range);
+ if (!first_hit.hit)
{
return;
}
auto pen_damage{ 0.0f };
- const auto can_pen = g_shared.pen( ).can( eye_pos, forward, pen_damage );
+ const auto can_pen = g_shared.pen().can(eye_pos, forward, pen_damage);
const auto& n = first_hit.normal;
- const auto ref = ( std::abs( n.z ) < 0.9f ) ? math::vector3{ 0.0f, 0.0f, 1.0f } : math::vector3{ 1.0f, 0.0f, 0.0f };
+ const auto ref = (std::abs(n.z) < 0.9f) ? math::vector3{ 0.0f, 0.0f, 1.0f } : math::vector3{ 1.0f, 0.0f, 0.0f };
- const auto d = ref.dot( n );
- const auto tangent = ( ref - n * d ).normalized( );
- const auto bitangent = n.cross( tangent );
+ const auto d = ref.dot(n);
+ const auto tangent = (ref - n * d).normalized();
+ const auto bitangent = n.cross(tangent);
const auto center = first_hit.end_pos + n * 0.05f;
constexpr auto half_size{ 3.5f };
- const math::vector3 corners[ 4 ]
+ const math::vector3 corners[4]
{
center - tangent * half_size - bitangent * half_size,
center + tangent * half_size - bitangent * half_size,
@@ -262,158 +694,229 @@ namespace features::combat {
center - tangent * half_size + bitangent * half_size,
};
- float sx[ 5 ]{}, sy[ 5 ]{};
+ float sx[5]{}, sy[5]{};
- for ( int i = 0; i < 4; ++i )
+ for (int i = 0; i < 4; ++i)
{
- const auto proj = systems::g_view.project( corners[ i ] );
- if ( !systems::g_view.projection_valid( proj ) )
+ const auto proj = systems::g_view.project(corners[i]);
+ if (!systems::g_view.projection_valid(proj))
{
return;
}
- sx[ i ] = proj.x;
- sy[ i ] = proj.y;
+ sx[i] = proj.x;
+ sy[i] = proj.y;
}
- const auto center_proj = systems::g_view.project( center );
- if ( !systems::g_view.projection_valid( center_proj ) )
+ const auto center_proj = systems::g_view.project(center);
+ if (!systems::g_view.projection_valid(center_proj))
{
return;
}
- sx[ 4 ] = center_proj.x;
- sy[ 4 ] = center_proj.y;
+ sx[4] = center_proj.x;
+ sy[4] = center_proj.y;
const auto& color = can_pen ? cfg.other.penetration_color_yes : cfg.other.penetration_color_no;
- const auto edge = zdraw::rgba{ color.r, color.g, color.b, static_cast< std::uint8_t >( color.a / 4 ) };
+ const auto edge = zdraw::rgba{ color.r, color.g, color.b, static_cast(color.a / 4) };
- for ( int i = 0; i < 4; ++i )
+ for (int i = 0; i < 4; ++i)
{
- const auto j = ( i + 1 ) % 4;
- draw_list.add_triangle_filled_multi_color( sx[ 4 ], sy[ 4 ], sx[ i ], sy[ i ], sx[ j ], sy[ j ], color, edge, edge );
+ const auto j = (i + 1) % 4;
+ draw_list.add_triangle_filled_multi_color(sx[4], sy[4], sx[i], sy[i], sx[j], sy[j], color, edge, edge);
}
- float screen[ 8 ]{ sx[ 0 ], sy[ 0 ], sx[ 1 ], sy[ 1 ], sx[ 2 ], sy[ 2 ], sx[ 3 ], sy[ 3 ] };
- draw_list.add_polyline( { screen, 8 }, { color.r, color.g, color.b, 255 }, true, 1.0f );
+ float screen[8]{ sx[0], sy[0], sx[1], sy[1], sx[2], sy[2], sx[3], sy[3] };
+ draw_list.add_polyline({ screen, 8 }, { color.r, color.g, color.b, 255 }, true, 1.0f);
+
+ if (cfg.other.penetration_damage && can_pen)
+ {
+ const auto text = std::format("{:.0f}", pen_damage);
+ zdraw::push_font(g::render.fonts().pretzel_12);
+ const auto [tw, th] = zdraw::measure_text(text.c_str());
+
+ draw_list.add_text(
+ sx[4] - (tw * 0.5f),
+ sy[4] + 8.0f,
+ text.c_str(),
+ zdraw::get_font(),
+ { 255, 255, 255, 220 },
+ zdraw::text_style::outlined
+ );
+
+ zdraw::pop_font();
+ }
}
- void legit::draw_fov( zdraw::draw_list& draw_list, const math::vector3& eye_pos, const math::vector3& view_angles, const settings::combat::aimbot& cfg )
+ void legit::draw_fov( zdraw::draw_list& draw_list, const math::vector3& eye_pos, const math::vector3& view_angles, const settings::combat::aimbot_settings& cfg )
{
- const auto target_radius = this->get_fov_radius( eye_pos, view_angles, static_cast< float >( cfg.fov ) );
- const auto alpha = this->m_fov_alpha.value( );
+ const auto target_radius = this->get_fov_radius(eye_pos, view_angles, static_cast(cfg.fov));
+ const auto alpha = this->m_fov_alpha.value();
const auto radius = target_radius * alpha;
- if ( radius <= 0.5f )
+ if (radius <= 0.5f)
{
return;
}
- const auto [w, h] = zdraw::get_display_size( );
- const auto color = zdraw::rgba{ cfg.fov_color.r, cfg.fov_color.g, cfg.fov_color.b, static_cast< std::uint8_t >( alpha * 125.0f ) };
+ const auto [w, h] = zdraw::get_display_size();
+ const auto color = zdraw::rgba{ cfg.fov_color.r, cfg.fov_color.g, cfg.fov_color.b, static_cast(alpha * 125.0f) };
- draw_list.add_circle( w * 0.5f, h * 0.5f, radius, color, 16 );
+ draw_list.add_circle(w * 0.5f, h * 0.5f, radius, color, 16);
}
- void legit::aimbot( const math::vector3& eye_pos, const math::vector3& view_angles, const target& tgt, const settings::combat::aimbot& cfg )
+ void legit::aimbot(const math::vector3& eye_pos, const math::vector3& view_angles, const target& tgt, const settings::combat::aimbot_settings& cfg)
{
- if ( !( GetAsyncKeyState( cfg.key ) & 0x8000 ) )
- {
+ HWND foreground = GetForegroundWindow();
+ HWND cs2_window = FindWindowA("SDL_app", "Counter-Strike 2");
+ if (cs2_window && foreground != cs2_window) {
this->m_aim_error = {};
return;
}
- constexpr auto m_yaw{ 0.022f };
- const auto sensitivity = systems::g_convars.get( CONVAR( "sensitivity"_hash ) );
- const auto fov_adjust = g::memory.read( systems::g_local.pawn( ) + SCHEMA( "C_BasePlayerPawn", "m_flFOVSensitivityAdjust"_hash ) );
- const auto deg_per_pixel = sensitivity * m_yaw * fov_adjust;
-
- if ( deg_per_pixel <= 0.0f )
- {
+ if (!(GetAsyncKeyState(cfg.key) & 0x8000)) {
+ this->m_aim_error = {};
return;
}
- const auto freshest = systems::g_bones.get( tgt.player->bone_cache );
- if ( !freshest.is_valid( ) )
- {
- return;
+ constexpr auto m_yaw = 0.022f;
+ const auto sensitivity = systems::g_convars.get(CONVAR("sensitivity"_hash));
+ const auto fov_adjust = g::memory.read(systems::g_local.pawn() + SCHEMA("C_BasePlayerPawn", "m_flFOVSensitivityAdjust"_hash));
+ const auto deg_per_pixel = sensitivity * m_yaw * fov_adjust;
+
+ if (deg_per_pixel <= 0.0f) return;
+
+ const auto freshest = systems::g_bones.get(tgt.player->bone_cache);
+ if (!freshest.is_valid()) return;
+
+ auto aim_point = freshest.get_position(tgt.bone) + tgt.offset;
+
+ if (cfg.predictive) {
+ const auto time = g_shared.get_prediction_time( );
+ aim_point += ( tgt.player->velocity * time ) + ( tgt.player->acceleration * 0.5f * time * time );
}
- auto aim_point = freshest.get_position( tgt.player->hitboxes.entries[ tgt.hitbox ].bone );
+ auto desired = math::helpers::calculate_angle(eye_pos, aim_point);
- if ( cfg.predictive )
- {
- const auto velocity = g::memory.read( tgt.player->pawn + SCHEMA( "C_BaseEntity", "m_vecAbsVelocity"_hash ) );
- const auto prediction_time = g_shared.get_prediction_time( );
+ math::helpers::normalize_angles(desired);
- aim_point = aim_point + velocity * prediction_time;
+ if (cfg.silent) {
+ for (int i = 0; i < 200; i++)
+ g::memory.write(systems::g_local.pawn() + SCHEMA("C_BasePlayerPawn", "v_angle"_hash), desired);
+ return;
+ }
+
+ if (cfg.rcs && cfg.rcs_factor > 0.0f) {
+ const auto aim_punch = g::memory.read(
+ systems::g_local.pawn() + SCHEMA("C_CSPlayerPawn", "m_aimPunchAngle"_hash)
+ );
+ const auto shots_fired = g::memory.read(
+ systems::g_local.pawn() + SCHEMA("C_CSPlayerPawn", "m_iShotsFired"_hash)
+ );
+
+ if (cfg.rcs && shots_fired > 1) {
+ desired.x -= aim_punch.x * cfg.rcs_factor * 2.0f;
+ desired.y -= aim_punch.y * cfg.rcs_factor * 1.5f;
+ }
}
- auto desired = math::helpers::calculate_angle( eye_pos, aim_point );
auto delta_x = desired.x - view_angles.x;
- auto delta_y = math::helpers::normalize_yaw( desired.y - view_angles.y );
+ auto delta_y = math::helpers::normalize_yaw(desired.y - view_angles.y);
- if ( cfg.smoothing > 1 )
+ if (cfg.smoothing > 1)
{
- const auto factor = static_cast< float >( cfg.smoothing );
+ const auto factor = static_cast(cfg.smoothing);
delta_x /= factor;
delta_y /= factor;
}
- const auto move_x = -delta_y / deg_per_pixel;
- const auto move_y = delta_x / deg_per_pixel;
+ const auto move_x = -delta_y / deg_per_pixel;
+ const auto move_y = delta_x / deg_per_pixel;
this->m_aim_error.x += move_x;
this->m_aim_error.y += move_y;
- const auto dx = static_cast< int >( this->m_aim_error.x );
- const auto dy = static_cast< int >( this->m_aim_error.y );
+ auto dx = static_cast(this->m_aim_error.x);
+ auto dy = static_cast(this->m_aim_error.y);
- this->m_aim_error.x -= static_cast< float >( dx );
- this->m_aim_error.y -= static_cast< float >( dy );
+ this->m_aim_error.x -= static_cast(dx);
+ this->m_aim_error.y -= static_cast(dy);
- if ( dx != 0 || dy != 0 )
- {
- g::input.inject_mouse( dx, dy, input::move );
+ if (dx != 0 || dy != 0) {
+ if (cfg.smoothing <= 1.0f) {
+ constexpr auto max_move = 120;
+ dx = std::clamp(dx, -max_move, max_move);
+ dy = std::clamp(dy, -max_move, max_move);
+ }
+
+ g::input.inject_mouse(dx, dy, input::move);
}
}
- legit::trigger_result legit::trace_crosshair( const math::vector3& eye_pos, const math::vector3& view_angles, const std::vector& players, const settings::combat::triggerbot& cfg ) const
+ legit::trigger_result legit::trace_crosshair(const math::vector3& eye_pos, const math::vector3& view_angles, const std::vector& players, const settings::combat::triggerbot_settings& cfg) const
{
trigger_result result{};
math::vector3 forward{};
- view_angles.to_directions( &forward, nullptr, nullptr );
+ view_angles.to_directions(&forward, nullptr, nullptr);
constexpr auto max_range{ 8192.0f };
const auto end_pos = eye_pos + forward * max_range;
- const auto world_trace = systems::g_bvh.trace_ray( eye_pos, end_pos );
+ const auto world_trace = systems::g_bvh.trace_ray(eye_pos, end_pos);
auto best_dist_sq = max_range * max_range;
- const auto prediction_time = cfg.predictive ? g_shared.get_prediction_time( ) + static_cast< float >( cfg.delay ) * 0.001f : 0.0f;
- for ( const auto& player : players )
+ auto is_hg_enabled = [&](int hg) {
+ switch (hg) {
+ case 1: return cfg.hitgroups.head;
+ case 2: return cfg.hitgroups.chest;
+ case 3: return cfg.hitgroups.stomach;
+ case 6: case 7: return cfg.hitgroups.arms;
+ case 4: case 5: return cfg.hitgroups.legs;
+ default: return false;
+ }
+ };
+
+ for (const auto& player : players)
{
- if ( !systems::g_local.is_enemy( player.team ) )
+ if (!systems::g_local.is_enemy(player.team) || !player.alive)
{
continue;
}
- if ( player.invulnerable || player.hitboxes.count <= 0 )
+ if (player.invulnerable || player.hitboxes.count <= 0)
{
continue;
}
- const auto bones = systems::g_bones.get( player.bone_cache );
- if ( !bones.is_valid( ) )
+ const auto bones = systems::g_bones.get(player.bone_cache);
+ if (!bones.is_valid())
{
continue;
}
- math::vector3 velocity{};
+ auto& history = this->m_prediction_history[ player.pawn ];
+
if ( cfg.predictive )
{
- velocity = g::memory.read( player.pawn + SCHEMA( "C_BaseEntity", "m_vecAbsVelocity"_hash ) );
+ const auto prediction_time = static_cast< float >( cfg.predictive_ms ) * 0.001f;
+ const auto prediction_offset = ( player.velocity * prediction_time ) + ( player.acceleration * 0.5f * prediction_time * prediction_time );
+
+ if ( history.smoothed_offset.length_sqr( ) == 0.0f )
+ {
+ history.smoothed_offset = prediction_offset;
+ }
+ else
+ {
+ constexpr float tick_dt = 1.0f / 64.0f;
+ history.smoothed_offset.x += ( prediction_offset.x - history.smoothed_offset.x ) * 25.0f * tick_dt;
+ history.smoothed_offset.y += ( prediction_offset.y - history.smoothed_offset.y ) * 25.0f * tick_dt;
+ history.smoothed_offset.z += ( prediction_offset.z - history.smoothed_offset.z ) * 25.0f * tick_dt;
+ }
+ }
+ else
+ {
+ history.smoothed_offset = {};
}
for ( const auto& hb : player.hitboxes )
@@ -423,33 +926,54 @@ namespace features::combat {
continue;
}
- const auto bone_pos = bones.get_position( hb.bone );
- const auto bone_rot = bones.get_rotation( hb.bone );
+ const auto hitgroup = systems::g_hitboxes.hitgroup_from_hitbox( hb.index );
+ if ( !is_hg_enabled( hitgroup ) )
+ {
+ continue;
+ }
+
+ const auto& bone = bones.bones[ hb.bone ];
+ const auto center_local = ( hb.mins + hb.maxs ) * 0.5f;
+ const auto center_world = bone.position + math::helpers::rotate_by_quat( bone.rotation, center_local ) + history.smoothed_offset;
- const auto capsule_start = bone_pos + bone_rot.rotate_vector( hb.mins ) + velocity * prediction_time;
- const auto capsule_end = bone_pos + bone_rot.rotate_vector( hb.maxs ) + velocity * prediction_time;
- const auto radius = ( hb.radius > 0.0f ? hb.radius : 3.5f ) * 0.85f;
+ const auto half_extent = (hb.maxs - hb.mins) * 0.5f;
+ const auto longest = std::max({ std::abs(half_extent.x), std::abs(half_extent.y), std::abs(half_extent.z) });
- if ( !g_shared.ray_hits_capsule( eye_pos, forward, capsule_start, capsule_end, radius ) )
+ math::vector3 axis_local{};
+ if (std::abs(half_extent.x) >= std::abs(half_extent.y) && std::abs(half_extent.x) >= std::abs(half_extent.z))
+ axis_local = { longest, 0.0f, 0.0f };
+ else if (std::abs(half_extent.y) >= std::abs(half_extent.z))
+ axis_local = { 0.0f, longest, 0.0f };
+ else
+ axis_local = { 0.0f, 0.0f, longest };
+
+ const auto axis_world = math::helpers::rotate_by_quat(bone.rotation, axis_local);
+ const auto capsule_start = center_world - axis_world;
+ const auto capsule_end = center_world + axis_world;
+
+ if (!ray_hits_capsule(eye_pos, forward, capsule_start, capsule_end, hb.radius))
{
continue;
}
- const auto capsule_center = ( capsule_start + capsule_end ) * 0.5f;
- const auto dist_sq = ( capsule_center - eye_pos ).length_sqr( );
+ const auto dist_sq = (center_world - eye_pos).length_sqr();
+
+ if (dist_sq >= best_dist_sq)
+ {
+ continue;
+ }
- if ( dist_sq >= best_dist_sq )
+ if (systems::g_voxels.line_goes_through_smoke(eye_pos, center_world))
{
continue;
}
- const auto vis_trace = systems::g_bvh.trace_ray( eye_pos, capsule_center );
- const auto visible = !vis_trace.hit || vis_trace.fraction > 0.97f;
+ const auto vis_trace = systems::g_bvh.trace_ray(eye_pos, center_world);
+ auto visible = !vis_trace.hit || vis_trace.fraction > 0.97f;
- if ( visible )
+ if (visible)
{
- const auto hitgroup = systems::g_hitboxes.hitgroup_from_hitbox( hb.index );
- const auto damage = combat::g_shared.pen( ).get_max_damage( hitgroup, player.armor, player.has_helmet, player.team );
+ const auto damage = combat::g_shared.pen().get_max_damage(hitgroup, player.armor, player.has_helmet, player.team);
best_dist_sq = dist_sq;
result.player = &player;
@@ -458,110 +982,419 @@ namespace features::combat {
result.hitgroup = hitgroup;
result.damage = damage;
result.penetrated = false;
+ result.sim_time = 0.0f;
+ result.smoothed_offset = history.smoothed_offset;
}
- else if ( cfg.autowall )
+ else if (cfg.autowall)
{
shared::penetration::result pen_result{};
- if ( combat::g_shared.pen( ).run( eye_pos, capsule_center, player, bones, pen_result ) )
+ if (combat::g_shared.pen().run(eye_pos, center_world, player, bones, pen_result))
{
- if ( pen_result.damage >= cfg.min_damage )
+ if (pen_result.damage >= cfg.min_damage)
{
best_dist_sq = dist_sq;
result.player = &player;
result.bones = bones;
result.hitbox = pen_result.hitbox;
- result.hitgroup = systems::g_hitboxes.hitgroup_from_hitbox( pen_result.hitbox );
+ result.hitgroup = systems::g_hitboxes.hitgroup_from_hitbox(pen_result.hitbox);
result.damage = pen_result.damage;
result.penetrated = pen_result.penetrated;
+ result.sim_time = 0.0f;
+ result.smoothed_offset = history.smoothed_offset;
}
}
}
}
+
+ if (result.player)
+ {
+ break;
+ }
}
return result;
}
- void legit::triggerbot( const math::vector3& eye_pos, const math::vector3& view_angles, const std::vector& players, const settings::combat::triggerbot& cfg )
- {
- if ( this->m_trigger_held )
- {
- return;
- }
-
- this->release_autostop( );
-
- if ( !( GetAsyncKeyState( cfg.key ) & 0x8000 ) )
- {
+ void legit::triggerbot(const math::vector3& eye_pos, const math::vector3& view_angles, const math::vector3& camera_angles, const std::vector& players, const settings::combat::triggerbot_settings& cfg)
+ {
+ HWND foreground = GetForegroundWindow();
+ HWND cs2_window = FindWindowA("SDL_app", "Counter-Strike 2");
+ if (cs2_window && foreground != cs2_window) {
+ this->release_autostop(true);
+ s_magnet_pawn = 0;
this->m_trigger_waiting = false;
return;
}
- const auto& ctx = g_shared.ctx( );
- if ( !ctx.weapon_ready )
+ if (!(GetAsyncKeyState(cfg.key) & 0x8000))
{
+ this->release_autostop(true);
+ s_magnet_pawn = 0;
this->m_trigger_waiting = false;
return;
}
- const auto pawn = systems::g_local.pawn( );
- const auto velocity = pawn ? g::memory.read( pawn + SCHEMA( "C_BaseEntity", "m_vecAbsVelocity"_hash ) ) : math::vector3{};
- const auto speed = velocity.length_2d( );
- const auto flags = pawn ? g::memory.read( pawn + SCHEMA( "C_BaseEntity", "m_fFlags"_hash ) ) : 0u;
- const auto on_ground = ( flags & 1 ) != 0;
+ this->release_autostop();
+
+ const auto pawn = systems::g_local.pawn();
+ const auto velocity = pawn ? g::memory.read(pawn + SCHEMA("C_BaseEntity", "m_vecAbsVelocity"_hash)) : math::vector3{};
+ const auto speed = velocity.length_2d();
+ const auto flags = pawn ? g::memory.read(pawn + SCHEMA("C_BaseEntity", "m_fFlags"_hash)) : 0u;
+ const auto on_ground = (flags & 1) != 0;
const auto is_moving = speed > 5.0f;
auto trace_pos = eye_pos;
auto found_at_extrapolated{ false };
- if ( cfg.autostop && cfg.early_autostop && on_ground && is_moving )
+ if (cfg.autostop && cfg.early_autostop && on_ground && is_moving)
{
constexpr auto lookahead_ticks{ 4 };
- const auto lookahead_pos = eye_pos + math::vector3{ velocity.x * cstypes::tick_interval * lookahead_ticks, velocity.y * cstypes::tick_interval * lookahead_ticks, 0.0f };
+ constexpr auto tick_interval{ 0.015625f };
+ const auto lookahead_pos = eye_pos + math::vector3{ velocity.x * tick_interval * lookahead_ticks, velocity.y * tick_interval * lookahead_ticks, 0.0f };
- trace_pos = g_shared.extrapolate_stop( lookahead_pos );
+ trace_pos = g_shared.extrapolate_stop(lookahead_pos);
- const auto extrap_result = this->trace_crosshair( trace_pos, view_angles, players, cfg );
- if ( extrap_result.player )
+ const auto extrap_result = this->trace_crosshair(trace_pos, camera_angles, players, cfg);
+ if (extrap_result.player)
{
found_at_extrapolated = true;
}
}
- const auto result = found_at_extrapolated ? this->trace_crosshair( trace_pos, view_angles, players, cfg ) : this->trace_crosshair( eye_pos, view_angles, players, cfg );
- if ( !result.player )
+ const auto result = found_at_extrapolated ? this->trace_crosshair(trace_pos, camera_angles, players, cfg) : this->trace_crosshair(eye_pos, camera_angles, players, cfg);
+
+ this->m_last_trigger_damage = result.damage;
+ this->m_min_trigger_damage = cfg.min_damage;
+
+ bool has_target = result.player != nullptr;
+ if (has_target && result.penetrated && result.damage < cfg.min_damage)
+ {
+ has_target = false;
+ }
+
+ if (has_target && cfg.magnet)
+ {
+ s_magnet_pawn = result.player->pawn;
+ }
+
+ float dist_to_center = 0.0f;
+
+ if (cfg.magnet && s_magnet_pawn != 0)
+ {
+ const systems::collector::player* target_ptr = nullptr;
+ systems::bones::data target_bones{};
+
+ for (const auto& p : players) {
+ if (p.pawn == s_magnet_pawn) {
+ target_ptr = &p;
+ target_bones = systems::g_bones.get(p.bone_cache);
+ if (!target_ptr->alive) {
+ s_magnet_pawn = 0;
+ }
+ break;
+ }
+ }
+
+ if (target_ptr && target_ptr->alive && target_ptr->health > 0 && !target_ptr->invulnerable && target_bones.is_valid())
+ {
+ math::vector3 aim_point{};
+ bool found_head = false;
+
+ for (const auto& hb : target_ptr->hitboxes)
+ {
+ if (hb.index < 0 || hb.bone < 0) continue;
+ if (systems::g_hitboxes.hitgroup_from_hitbox(hb.index) == 1) // head
+ {
+ const auto bone_pos = target_bones.get_position(hb.bone);
+ const auto bone_rot = target_bones.get_rotation(hb.bone);
+ const auto local_center = (hb.mins + hb.maxs) * 0.5f;
+ aim_point = bone_pos + bone_rot.rotate_vector(local_center);
+ found_head = true;
+ break;
+ }
+ }
+
+ if (found_head)
+ {
+ const auto smoked = systems::g_voxels.line_goes_through_smoke(eye_pos, aim_point);
+
+ if (smoked)
+ {
+ s_magnet_pawn = 0;
+ }
+ else
+ {
+ const auto trace = systems::g_bvh.trace_ray(eye_pos, aim_point);
+ const auto visible = !trace.hit || trace.fraction > 0.97f;
+
+ if (!visible && !cfg.autowall)
+ {
+ s_magnet_pawn = 0;
+ }
+ else
+ {
+ if (cfg.predictive) {
+ const auto prediction_time = static_cast(cfg.predictive_ms) * 0.001f;
+ const auto prediction_offset = (target_ptr->velocity * prediction_time) + (target_ptr->acceleration * 0.5f * prediction_time * prediction_time);
+
+ auto& history = this->m_prediction_history[target_ptr->pawn];
+ if (history.smoothed_offset.length_sqr() == 0.0f)
+ {
+ history.smoothed_offset = prediction_offset;
+ }
+ else
+ {
+ constexpr float tick_dt = 1.0f / 64.0f;
+ history.smoothed_offset.x += (prediction_offset.x - history.smoothed_offset.x) * 25.0f * tick_dt;
+ history.smoothed_offset.y += (prediction_offset.y - history.smoothed_offset.y) * 25.0f * tick_dt;
+ history.smoothed_offset.z += (prediction_offset.z - history.smoothed_offset.z) * 25.0f * tick_dt;
+ }
+
+ aim_point += history.smoothed_offset;
+ }
+
+ if (this->get_fov(camera_angles, eye_pos, aim_point) > 35.0f)
+ {
+ s_magnet_pawn = 0;
+ }
+ else
+ {
+ auto desired = math::helpers::calculate_angle(eye_pos, aim_point);
+
+ const auto pawn_ptr = systems::g_local.pawn();
+ const auto aim_punch = pawn_ptr ? g::memory.read(pawn_ptr + SCHEMA("C_CSPlayerPawn", "m_aimPunchAngle"_hash)) : math::vector3{};
+ const auto shots_fired = pawn_ptr ? g::memory.read(pawn_ptr + SCHEMA("C_CSPlayerPawn", "m_iShotsFired"_hash)) : 0;
+
+ if (shots_fired > 0) {
+ desired.x -= aim_punch.x * 2.0f;
+ desired.y -= aim_punch.y * 2.0f;
+ }
+
+ math::helpers::normalize_angles(desired);
+
+ auto delta_x = desired.x - camera_angles.x;
+ auto delta_y = math::helpers::normalize_yaw(desired.y - camera_angles.y);
+
+ dist_to_center = std::sqrtf(delta_x * delta_x + delta_y * delta_y);
+
+ if (cfg.magnet_smoothing > 1)
+ {
+ const auto factor = static_cast(cfg.magnet_smoothing);
+ delta_x /= factor;
+ delta_y /= factor;
+ }
+
+ const auto sensitivity = systems::g_convars.get(CONVAR("sensitivity"_hash));
+ const auto fov_adjust = pawn_ptr ? g::memory.read(pawn_ptr + SCHEMA("C_BasePlayerPawn", "m_flFOVSensitivityAdjust"_hash)) : 1.0f;
+ const auto deg_per_pixel = sensitivity * 0.022f * fov_adjust;
+
+ if (deg_per_pixel > 0.0f) {
+ const auto move_x = -delta_y / deg_per_pixel;
+ const auto move_y = delta_x / deg_per_pixel;
+
+ this->m_aim_error.x += move_x;
+ this->m_aim_error.y += move_y;
+
+ auto dx = static_cast(this->m_aim_error.x);
+ auto dy = static_cast(this->m_aim_error.y);
+
+ this->m_aim_error.x -= static_cast(dx);
+ this->m_aim_error.y -= static_cast(dy);
+
+ if (dx != 0 || dy != 0) {
+ if (cfg.magnet_smoothing <= 1) {
+ dx = std::clamp(dx, -240, 240);
+ dy = std::clamp(dy, -240, 240);
+ }
+ g::input.inject_mouse(dx, dy, input::move);
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ s_magnet_pawn = 0;
+ }
+ }
+ }
+
+ if (!has_target && !cfg.seed_triggerbot)
{
this->m_trigger_waiting = false;
return;
}
- if ( result.penetrated && result.damage < cfg.min_damage )
+ if (cfg.magnet && (!has_target || result.hitgroup != 1 || dist_to_center > 0.75f))
{
this->m_trigger_waiting = false;
return;
}
- if ( cfg.autostop && on_ground && is_moving )
+ if (this->m_trigger_held)
+ {
+ return;
+ }
+
+ if (cfg.autostop && on_ground && is_moving && has_target)
{
- this->apply_autostop( );
+ this->apply_autostop();
- if ( speed > 30.0f )
+ if (speed > 30.0f)
{
return;
}
}
- if ( !g_shared.is_sniper_accurate( ) )
+ const auto& ctx = g_shared.ctx();
+ if (!ctx.weapon_ready)
{
+ this->m_trigger_waiting = false;
return;
}
- if ( cfg.hitchance > 0.0f )
+ const bool is_autowall_hit = has_target && result.penetrated;
+
+ bool seed_hit = false;
+ if (cfg.seed_triggerbot)
+ {
+ constexpr auto required_ticks{ 1 };
+ auto all_ticks_hit{ true };
+
+ const auto aim_punch = g::memory.read(pawn + SCHEMA("C_CSPlayerPawn", "m_aimPunchAngle"_hash));
+ const auto view = view_angles + aim_punch;
+
+ constexpr auto deg2rad = std::numbers::pi_v / 180.f;
+ const auto sp = std::sinf(view.x * deg2rad);
+ const auto cp = std::cosf(view.x * deg2rad);
+ const auto sy = std::sinf(view.y * deg2rad);
+ const auto cy = std::cosf(view.y * deg2rad);
+
+ const math::vector3 fwd{ cp * cy, cp * sy, -sp };
+ const math::vector3 right{ -sy, cy, 0.f };
+ const math::vector3 up{ sp * cy, sp * sy, cp };
+
+ auto is_hg_enabled_seed = [&](int hg) {
+ switch (hg) {
+ case 1: return cfg.hitgroups.head;
+ case 2: return cfg.hitgroups.chest;
+ case 3: return cfg.hitgroups.stomach;
+ case 6: case 7: return cfg.hitgroups.arms;
+ case 4: case 5: return cfg.hitgroups.legs;
+ default: return false;
+ }
+ };
+
+
+ const auto render_tick = g::memory.read(systems::g_local.controller() + SCHEMA("CBasePlayerController", "m_nTickBase"_hash));
+ static auto prev_render_tick{ 0 };
+
+ prev_render_tick = render_tick;
+
+ for (int tick_offset = 0; tick_offset < required_ticks; ++tick_offset)
+ {
+ const auto seed = g_shared.get_spread_seed(view_angles, render_tick - 1 + tick_offset);
+ const auto sv = g_shared.calculate_spread(seed + 1, ctx.inaccuracy, ctx.spread, ctx.recoil_index, ctx.item_def_idx, 0);
+ const auto dir = (fwd + right * -sv.x + up * sv.y).normalized();
+
+ auto hit_any{ false };
+
+ for (const auto& target : players)
+ {
+ if (!systems::g_local.is_enemy(target.team) || target.health <= 0 || target.invulnerable || target.hitboxes.count <= 0)
+ continue;
+
+ const auto bone_data = systems::g_bones.get(target.bone_cache);
+ if (!bone_data.is_valid())
+ continue;
+
+ for (const auto& hb : target.hitboxes)
+ {
+ if (hb.index < 0 || hb.bone < 0 || hb.radius <= 0.0f)
+ continue;
+
+ if (!is_hg_enabled_seed(systems::g_hitboxes.hitgroup_from_hitbox(hb.index)))
+ continue;
+
+ const auto& bone = bone_data.bones[hb.bone];
+ const auto center_local = (hb.mins + hb.maxs) * 0.5f;
+ const auto center_world = bone.position + math::helpers::rotate_by_quat(bone.rotation, center_local);
+
+ const auto half_extent = (hb.maxs - hb.mins) * 0.5f;
+ const auto longest = std::max({ std::abs(half_extent.x), std::abs(half_extent.y), std::abs(half_extent.z) });
+
+ math::vector3 axis_local{};
+ if (std::abs(half_extent.x) >= std::abs(half_extent.y) && std::abs(half_extent.x) >= std::abs(half_extent.z))
+ axis_local = { longest, 0.0f, 0.0f };
+ else if (std::abs(half_extent.y) >= std::abs(half_extent.z))
+ axis_local = { 0.0f, longest, 0.0f };
+ else
+ axis_local = { 0.0f, 0.0f, longest };
+
+ const auto axis_world = math::helpers::rotate_by_quat(bone.rotation, axis_local);
+ const auto capsule_start = center_world - axis_world;
+ const auto capsule_end = center_world + axis_world;
+
+ if (ray_hits_capsule(eye_pos, dir, capsule_start, capsule_end, hb.radius))
+ {
+
+ shared::penetration::result pen_result{};
+ if (g_shared.pen().run(eye_pos, eye_pos + dir * 8192.0f, target, bone_data, pen_result))
+ {
+ const auto required_damage = is_autowall_hit ? 1.0f : cfg.min_damage;
+ if (pen_result.damage >= required_damage)
+ {
+ hit_any = true;
+ this->m_last_trigger_damage = pen_result.damage;
+ break;
+ }
+ }
+ }
+ }
+
+ if (hit_any)
+ break;
+ }
+
+ if (!hit_any)
+ {
+ all_ticks_hit = false;
+ break;
+ }
+ }
+
+ if (all_ticks_hit)
+ {
+ seed_hit = true;
+ }
+ }
+
+ if (cfg.seed_triggerbot && !seed_hit)
+ {
+ if (has_target && cfg.hitchance > 0.0f)
+ {
+ const auto required = cfg.hitchance / 100.0f;
+ const auto hc = g_shared.calculate_hitchance(eye_pos, camera_angles, *result.player, result.bones, result.smoothed_offset);
+
+ if (hc < required)
+ {
+ this->m_trigger_waiting = false;
+ return;
+ }
+ }
+ else
+ {
+ this->m_trigger_waiting = false;
+ return;
+ }
+ }
+ else if (!cfg.seed_triggerbot && cfg.hitchance > 0.0f)
{
const auto required = cfg.hitchance / 100.0f;
- const auto hc = g_shared.calculate_hitchance( eye_pos, view_angles, *result.player, result.bones );
+ const auto hc = g_shared.calculate_hitchance(eye_pos, camera_angles, *result.player, result.bones, result.smoothed_offset);
- if ( hc < required )
+ if (hc < required)
{
this->m_trigger_waiting = false;
return;
@@ -570,113 +1403,162 @@ namespace features::combat {
const auto now = ctx.current_time;
- if ( !this->m_trigger_waiting )
+ if (!this->m_trigger_waiting)
{
this->m_trigger_waiting = true;
- this->m_trigger_delay_end = now + static_cast< float >( cfg.delay ) * 0.001f;
+ this->m_trigger_delay_end = now + static_cast(cfg.delay) * 0.001f;
return;
}
- if ( now < this->m_trigger_delay_end )
+ if (now < this->m_trigger_delay_end)
{
return;
}
this->m_trigger_waiting = false;
- const auto hold_ms = this->m_rng.random_float( 50.0f, 120.0f );
+ const auto hold_ms = this->m_rng.random_float(50.0f, 120.0f);
- g::input.inject_mouse( 0, 0, input::left_down );
+ g::input.inject_mouse(0, 0, input::left_down);
this->m_trigger_held = true;
this->m_trigger_release_time = now + hold_ms * 0.001f;
}
- void legit::apply_autostop( )
+ void legit::apply_autostop()
{
- const auto pawn = systems::g_local.pawn( );
- if ( !pawn )
+ const auto pawn = systems::g_local.pawn();
+ if (!pawn)
{
return;
}
- const auto flags = g::memory.read( pawn + SCHEMA( "C_BaseEntity", "m_fFlags"_hash ) );
- if ( !( flags & ( 1 << 0 ) ) )
+ const auto flags = g::memory.read(pawn + SCHEMA("C_BaseEntity", "m_fFlags"_hash));
+ if (!(flags & (1 << 0)))
{
return;
}
- const auto velocity = g::memory.read( pawn + SCHEMA( "C_BaseEntity", "m_vecAbsVelocity"_hash ) );
- if ( velocity.length_2d( ) <= 15.0f )
+ const auto velocity = g::memory.read(pawn + SCHEMA("C_BaseEntity", "m_vecAbsVelocity"_hash));
+ const float speed = velocity.length_2d();
+ if (speed <= 13.0f)
{
return;
}
- if ( this->m_autostop_active )
+ if (this->m_autostop_active)
{
return;
}
- const auto forward_pressed = ( GetAsyncKeyState( 'W' ) & 0x8000 ) != 0;
- const auto back_pressed = ( GetAsyncKeyState( 'S' ) & 0x8000 ) != 0;
- const auto left_pressed = ( GetAsyncKeyState( 'A' ) & 0x8000 ) != 0;
- const auto right_pressed = ( GetAsyncKeyState( 'D' ) & 0x8000 ) != 0;
+ const auto angles = systems::g_view.angles();
+ math::vector3 forward, right;
+ angles.to_directions(&forward, &right, nullptr);
- this->m_autostop_keys.clear( );
+ // Get current velocity vectors relative to view
+ const float fwd_vel = velocity.dot(forward);
+ const float side_vel = velocity.dot(right);
- if ( forward_pressed && !back_pressed )
- {
- this->m_autostop_keys.push_back( 'S' );
+ this->m_autostop_keys.clear();
+ this->m_autostop_inhibited_keys.clear();
+
+ auto inhibit_key = [&](std::uint16_t key) {
+ if (GetAsyncKeyState(key) & 0x8000) {
+ g::input.inject_keyboard(key, false);
+ this->m_autostop_inhibited_keys.push_back(key);
+ }
+ };
+
+ // Determine counter-strafe keys
+ if (fwd_vel > 13.0f) {
+ this->m_autostop_keys.push_back('S');
+ inhibit_key('W');
}
- else if ( back_pressed && !forward_pressed )
- {
- this->m_autostop_keys.push_back( 'W' );
+ else if (fwd_vel < -13.0f) {
+ this->m_autostop_keys.push_back('W');
+ inhibit_key('S');
}
- if ( left_pressed && !right_pressed )
- {
- this->m_autostop_keys.push_back( 'D' );
+ if (side_vel > 13.0f) {
+ this->m_autostop_keys.push_back('A');
+ inhibit_key('D');
}
- else if ( right_pressed && !left_pressed )
- {
- this->m_autostop_keys.push_back( 'A' );
+ else if (side_vel < -13.0f) {
+ this->m_autostop_keys.push_back('D');
+ inhibit_key('A');
}
- if ( this->m_autostop_keys.empty( ) )
+ if (this->m_autostop_keys.empty())
{
return;
}
- for ( const auto key : this->m_autostop_keys )
+ for (const auto key : this->m_autostop_keys)
{
- g::input.inject_keyboard( key, true );
+ g::input.inject_keyboard(key, true);
}
this->m_autostop_active = true;
- this->m_autostop_start = std::chrono::steady_clock::now( );
+ this->m_autostop_start = std::chrono::steady_clock::now();
}
- void legit::release_autostop( )
+ void legit::release_autostop(bool force)
{
- if ( !this->m_autostop_active )
+ if (!this->m_autostop_active)
+ {
+ return;
+ }
+
+ const auto pawn = systems::g_local.pawn();
+ if (!pawn && !force)
{
return;
}
- const auto pawn = systems::g_local.pawn( );
- const auto velocity = pawn ? g::memory.read( pawn + SCHEMA( "C_BaseEntity", "m_vecAbsVelocity"_hash ) ) : math::vector3{};
- const auto elapsed = std::chrono::duration( std::chrono::steady_clock::now( ) - this->m_autostop_start ).count( );
+ const auto velocity = pawn ? g::memory.read(pawn + SCHEMA("C_BaseEntity", "m_vecAbsVelocity"_hash)) : math::vector3{};
+ const float speed = velocity.length_2d();
+
+ const auto angles = systems::g_view.angles();
+ math::vector3 forward, right;
+ angles.to_directions(&forward, &right, nullptr);
+
+ const float fwd_vel = velocity.dot(forward);
+ const float side_vel = velocity.dot(right);
+
+ bool should_release = force || speed < 13.0f;
- if ( velocity.length_2d( ) > 15.0f && elapsed < 0.15f )
+ if (!should_release)
+ {
+ for (const auto key : this->m_autostop_keys)
+ {
+ if (key == 'S' && fwd_vel <= 0.0f) should_release = true;
+ else if (key == 'W' && fwd_vel >= 0.0f) should_release = true;
+ else if (key == 'A' && side_vel <= 0.0f) should_release = true;
+ else if (key == 'D' && side_vel >= 0.0f) should_release = true;
+
+ if (should_release)
+ break;
+ }
+ }
+
+ if (!should_release)
{
return;
}
- for ( const auto key : this->m_autostop_keys )
+ for (const auto key : this->m_autostop_keys)
{
- g::input.inject_keyboard( key, false );
+ g::input.inject_keyboard(key, false);
+ }
+
+ for (const auto key : this->m_autostop_inhibited_keys)
+ {
+ if (GetAsyncKeyState(key) & 0x8000) {
+ g::input.inject_keyboard(key, true);
+ }
}
- this->m_autostop_keys.clear( );
+ this->m_autostop_keys.clear();
+ this->m_autostop_inhibited_keys.clear();
this->m_autostop_active = false;
}
diff --git a/catalyst/project/core/features/impl/combat/shared.cpp b/catalyst/project/core/features/impl/combat/shared.cpp
index a66d928..7408986 100644
--- a/catalyst/project/core/features/impl/combat/shared.cpp
+++ b/catalyst/project/core/features/impl/combat/shared.cpp
@@ -1,4 +1,4 @@
-#include
+#include
namespace features::combat {
@@ -33,6 +33,61 @@ namespace features::combat {
return v / ( ( ( 1.0f / c - 2.0f ) * ( 1.0f - v ) ) + 1.0f );
}
+ static bool ray_hits_capsule( const math::vector3& ray_origin, const math::vector3& ray_dir, const math::vector3& capsule_start, const math::vector3& capsule_end, float radius )
+ {
+ const auto capsule_vec = capsule_end - capsule_start;
+ const auto capsule_length = capsule_vec.length( );
+
+ if ( capsule_length < 0.001f )
+ {
+ const auto to_center = capsule_start - ray_origin;
+ const auto projection = to_center.dot( ray_dir );
+
+ if ( projection < 0.0f )
+ {
+ return false;
+ }
+
+ const auto closest = ray_origin + ray_dir * projection;
+ return ( closest - capsule_start ).length_sqr( ) <= radius * radius;
+ }
+
+ const auto capsule_dir = capsule_vec / capsule_length;
+ const auto w = ray_origin - capsule_start;
+
+ const auto a = ray_dir.dot( ray_dir );
+ const auto b = ray_dir.dot( capsule_dir );
+ const auto c = capsule_dir.dot( capsule_dir );
+ const auto d = ray_dir.dot( w );
+ const auto e = capsule_dir.dot( w );
+
+ const auto denom = a * c - b * b;
+
+ float s, t;
+
+ if ( std::abs( denom ) < 0.0001f )
+ {
+ s = 0.0f;
+ t = ( b > c ? d / b : e / c );
+ }
+ else
+ {
+ s = ( b * e - c * d ) / denom;
+ t = ( a * e - b * d ) / denom;
+ }
+
+ t = std::clamp( t, 0.0f, capsule_length );
+ if ( s < 0.0f )
+ {
+ return false;
+ }
+
+ const auto point_on_capsule = capsule_start + capsule_dir * t;
+ const auto point_on_ray = ray_origin + ray_dir * s;
+
+ return ( point_on_ray - point_on_capsule ).length_sqr( ) <= radius * radius;
+ }
+
static void scale_damage( int hitgroup, int armor, bool has_helmet, int team, float armor_ratio, float headshot_multiplier, float& damage )
{
const auto ct_head = systems::g_convars.get( CONVAR( "mp_damage_scale_ct_head"_hash ) );
@@ -128,9 +183,13 @@ namespace features::combat {
auto check_target = [ & ]( const math::vector3& seg_start, float seg_start_dist, float seg_end_dist ) -> bool
{
+ int closest_hb = -1;
+ float best_dist = 1e18f;
+ float best_damage = 0.0f;
+
for ( const auto& hb : target.hitboxes )
{
- if ( hb.index < 0 || hb.bone < 0 )
+ if ( hb.index < 0 || hb.bone < 0 || hb.radius <= 0.0f )
{
continue;
}
@@ -144,23 +203,17 @@ namespace features::combat {
math::vector3 axis_local{};
if ( std::abs( half_extent.x ) >= std::abs( half_extent.y ) && std::abs( half_extent.x ) >= std::abs( half_extent.z ) )
- {
axis_local = { longest, 0.0f, 0.0f };
- }
else if ( std::abs( half_extent.y ) >= std::abs( half_extent.z ) )
- {
axis_local = { 0.0f, longest, 0.0f };
- }
else
- {
axis_local = { 0.0f, 0.0f, longest };
- }
const auto axis_world = math::helpers::rotate_by_quat( bone.rotation, axis_local );
const auto capsule_start = center_world - axis_world;
const auto capsule_end = center_world + axis_world;
- if ( !g_shared.ray_hits_capsule( seg_start, direction, capsule_start, capsule_end, hb.radius ) )
+ if ( !detail::ray_hits_capsule( seg_start, direction, capsule_start, capsule_end, hb.radius ) )
{
continue;
}
@@ -173,30 +226,30 @@ namespace features::combat {
continue;
}
- if ( current_damage < 1.0f )
+ if ( hit_dist < best_dist )
{
- continue;
- }
+ const auto total_dist = seg_start_dist + hit_dist;
+ auto damage = current_damage * std::pow( this->m_weapon_data.range_modifier, total_dist / 500.0f ); // Falloff is usually per 500 units in CS
- const auto total_dist = seg_start_dist + hit_dist;
- auto damage = current_damage * std::pow( this->m_weapon_data.range_modifier, total_dist / max_range );
-
- if ( damage < 1.0f )
- {
- continue;
- }
+ if ( damage < 1.0f )
+ continue;
- const auto hitgroup = systems::g_hitboxes.hitgroup_from_hitbox( hb.index );
+ const auto hitgroup = systems::g_hitboxes.hitgroup_from_hitbox( hb.index );
+ detail::scale_damage( hitgroup, target.armor, target.has_helmet, target.team, this->m_weapon_data.armor_ratio, this->m_weapon_data.headshot_multiplier, damage );
- detail::scale_damage( hitgroup, target.armor, target.has_helmet, target.team, this->m_weapon_data.armor_ratio, this->m_weapon_data.headshot_multiplier, damage );
-
- if ( damage < 1.0f )
- {
- continue;
+ if ( damage >= 1.0f )
+ {
+ best_dist = hit_dist;
+ closest_hb = hb.index;
+ best_damage = damage;
+ }
}
+ }
- out.damage = damage;
- out.hitbox = hb.index;
+ if ( closest_hb != -1 )
+ {
+ out.damage = best_damage;
+ out.hitbox = closest_hb;
out.penetrated = ( penetration_count < 4 );
return true;
}
@@ -284,11 +337,11 @@ namespace features::combat {
return false;
}
- bool shared::penetration::can( const math::vector3& start, const math::vector3& direction, float& out_damage ) const
+ bool shared::penetration::can(const math::vector3& start, const math::vector3& direction, float& out_damage) const
{
out_damage = 0.0f;
- if ( this->m_weapon_data.damage <= 0.0f )
+ if (this->m_weapon_data.damage <= 0.0f)
{
return false;
}
@@ -296,18 +349,18 @@ namespace features::combat {
const auto max_range = this->m_weapon_data.range;
const auto ray_end = start + direction * max_range;
- const auto first_hit = systems::g_bvh.trace_ray( start, ray_end );
- if ( !first_hit.hit )
+ const auto first_hit = systems::g_bvh.trace_ray(start, ray_end);
+ if (!first_hit.hit)
{
return false;
}
- const auto all_hits = systems::g_bvh.trace_ray_all( start, ray_end );
- const auto segments = systems::g_bvh.build_segments( all_hits, max_range );
+ const auto all_hits = systems::g_bvh.trace_ray_all(start, ray_end);
+ const auto segments = systems::g_bvh.build_segments(all_hits, max_range);
- if ( segments.empty( ) )
+ if (segments.empty())
{
- if ( first_hit.surface.penetration >= 0.1f && this->m_weapon_data.penetration > 0.0f )
+ if (first_hit.surface.penetration >= 0.1f && this->m_weapon_data.penetration > 0.0f)
{
out_damage = this->m_weapon_data.damage;
return true;
@@ -316,38 +369,38 @@ namespace features::combat {
return false;
}
- const auto& seg = segments[ 0 ];
+ const auto& seg = segments[0];
auto pen_mod = seg.min_pen_mod;
const auto enter_type = seg.enter_surface.surface_type;
const auto exit_type = seg.exit_surface.surface_type;
- if ( enter_type != exit_type )
+ if (enter_type != exit_type)
{
- pen_mod = std::min( pen_mod, seg.exit_surface.penetration );
+ pen_mod = std::min(pen_mod, seg.exit_surface.penetration);
}
- if ( seg.exit_distance > 3000.0f || pen_mod < 0.1f )
+ if (seg.exit_distance > 3000.0f || pen_mod < 0.1f)
{
return false;
}
auto damage_modifier = 0.16f;
- if ( pen_mod >= 0.1f && enter_type == exit_type )
+ if (pen_mod >= 0.1f && enter_type == exit_type)
{
- if ( ( ( enter_type - 85 ) & 0xfffffffd ) == 0 )
+ if (((enter_type - 85) & 0xfffffffd) == 0)
{
pen_mod = 3.0f;
}
- else if ( enter_type == 76 )
+ else if (enter_type == 76)
{
pen_mod = 2.0f;
}
- if ( seg.thickness < 6.0f )
+ if (seg.thickness < 6.0f)
{
- if ( enter_type == 71 || enter_type == 89 )
+ if (enter_type == 71 || enter_type == 89)
{
damage_modifier = 0.05f;
pen_mod = 3.0f;
@@ -357,11 +410,11 @@ namespace features::combat {
const auto inv_pen = 1.0f / pen_mod;
const auto base_loss = damage_modifier * this->m_weapon_data.damage;
- const auto pen_loss = std::max( 0.0f, ( 3.0f / this->m_weapon_data.penetration ) * 1.25f ) * ( inv_pen * 3.0f );
- const auto dist_loss = ( seg.thickness * seg.thickness * inv_pen ) / 24.0f;
- const auto remaining = this->m_weapon_data.damage - ( base_loss + pen_loss + dist_loss );
+ const auto pen_loss = std::max(0.0f, (3.0f / this->m_weapon_data.penetration) * 1.25f) * (inv_pen * 3.0f);
+ const auto dist_loss = (seg.thickness * seg.thickness * inv_pen) / 24.0f;
+ const auto remaining = this->m_weapon_data.damage - (base_loss + pen_loss + dist_loss);
- if ( remaining < 1.0f )
+ if (remaining < 1.0f)
{
return false;
}
@@ -429,7 +482,9 @@ namespace features::combat {
ctx.weapon_type = g::memory.read( ctx.weapon_vdata + SCHEMA( "CCSWeaponBaseVData", "m_WeaponType"_hash ) );
ctx.item_def_idx = g::memory.read( ctx.weapon + SCHEMA( "C_EconEntity", "m_AttributeManager"_hash ) + SCHEMA( "C_AttributeContainer", "m_Item"_hash ) + SCHEMA( "C_EconItemView", "m_iItemDefinitionIndex"_hash ) );
ctx.num_bullets = g::memory.read( ctx.weapon_vdata + SCHEMA( "CCSWeaponBaseVData", "m_nNumBullets"_hash ) );
- ctx.inaccuracy = this->get_inaccuracy( local_pawn, ctx.weapon, ctx.weapon_vdata, systems::g_view.angles( ) );
+ ctx.accuracy_penalty = g::memory.read( ctx.weapon + SCHEMA( "C_CSWeaponBase", "m_fAccuracyPenalty"_hash ) );
+
+ ctx.inaccuracy = this->get_inaccuracy( local_pawn, ctx.weapon, ctx.weapon_vdata, systems::g_view.angles(), nullptr, nullptr);
ctx.spread = this->get_spread( ctx.weapon_vdata );
ctx.recoil_index = g::memory.read( ctx.weapon + SCHEMA( "C_CSWeaponBase", "m_flRecoilIndex"_hash ) );
ctx.is_reloading = g::memory.read( ctx.weapon + SCHEMA( "C_CSWeaponBase", "m_bInReload"_hash ) );
@@ -462,7 +517,7 @@ namespace features::combat {
this->m_ctx = ctx;
}
- float shared::calculate_hitchance( const math::vector3& eye_pos, const math::vector3& aim_angle, const systems::collector::player& target, const systems::bones::data& bones ) const
+ float shared::calculate_hitchance( const math::vector3& eye_pos, const math::vector3& aim_angle, const systems::collector::player& target, const systems::bones::data& bones, const math::vector3& offset ) const
{
const auto& ctx = this->m_ctx;
const auto total_spread = ctx.spread + ctx.inaccuracy;
@@ -478,73 +533,51 @@ namespace features::combat {
return 0.0f;
}
- struct capsule_t
- {
- math::vector3 start;
- math::vector3 end;
- float radius;
- };
-
- std::array capsules;
- auto capsule_count{ 0 };
-
- for ( const auto& hb : target.hitboxes )
- {
- if ( hb.index < 0 || hb.bone < 0 )
- {
- continue;
- }
-
- const auto& bone = bones.bones[ hb.bone ];
-
- const auto center_local = ( hb.mins + hb.maxs ) * 0.5f;
- const auto half_extent = ( hb.maxs - hb.mins ) * 0.5f;
-
- const auto ax = std::abs( half_extent.x );
- const auto ay = std::abs( half_extent.y );
- const auto az = std::abs( half_extent.z );
- const auto longest = std::max( { ax, ay, az } );
-
- math::vector3 axis_local;
-
- if ( ax >= ay && ax >= az )
- {
- axis_local = { longest, 0.0f, 0.0f };
- }
- else if ( ay >= az )
- {
- axis_local = { 0.0f, longest, 0.0f };
- }
- else
- {
- axis_local = { 0.0f, 0.0f, longest };
- }
-
- const auto center_world = bone.position + bone.rotation.rotate_vector( center_local );
- const auto axis_world = bone.rotation.rotate_vector( axis_local );
-
- capsules[ capsule_count++ ] = { center_world - axis_world, center_world + axis_world, hb.radius };
- }
-
- if ( capsule_count == 0 )
- {
- return 0.0f;
- }
-
math::vector3 forward{}, right{}, up{};
aim_angle.to_directions( &forward, &right, &up );
- constexpr auto samples{ 256 };
+ constexpr auto samples{ 1024 };
auto hits{ 0 };
for ( int seed = 0; seed < samples; ++seed )
{
const auto spread = this->calculate_spread( seed, ctx.inaccuracy, ctx.spread, ctx.recoil_index, ctx.item_def_idx, ctx.num_bullets );
- const auto direction = ( forward + right * spread.x + up * spread.y ).normalized( );
+ auto direction = forward + ( right * spread.x ) + ( up * spread.y );
+ direction = direction.normalized( );
- for ( int i = 0; i < capsule_count; ++i )
+ for ( const auto& hb : target.hitboxes )
{
- if ( this->ray_hits_capsule( eye_pos, direction, capsules[ i ].start, capsules[ i ].end, capsules[ i ].radius ) )
+ if ( hb.index < 0 || hb.bone < 0 )
+ {
+ continue;
+ }
+
+ const auto& bone = bones.bones[ hb.bone ];
+ const auto center_local = ( hb.mins + hb.maxs ) * 0.5f;
+ const auto center_world = bone.position + math::helpers::rotate_by_quat( bone.rotation, center_local ) + offset;
+
+ const auto half_extent = ( hb.maxs - hb.mins ) * 0.5f;
+ const auto longest = std::max( { std::abs( half_extent.x ), std::abs( half_extent.y ), std::abs( half_extent.z ) } );
+
+ math::vector3 axis_local{};
+ if ( std::abs( half_extent.x ) >= std::abs( half_extent.y ) && std::abs( half_extent.x ) >= std::abs( half_extent.z ) )
+ {
+ axis_local = { longest, 0.0f, 0.0f };
+ }
+ else if ( std::abs( half_extent.y ) >= std::abs( half_extent.z ) )
+ {
+ axis_local = { 0.0f, longest, 0.0f };
+ }
+ else
+ {
+ axis_local = { 0.0f, 0.0f, longest };
+ }
+
+ const auto axis_world = math::helpers::rotate_by_quat( bone.rotation, axis_local );
+ const auto capsule_start = center_world - axis_world;
+ const auto capsule_end = center_world + axis_world;
+
+ if ( detail::ray_hits_capsule( eye_pos, direction, capsule_start, capsule_end, hb.radius ) )
{
++hits;
break;
@@ -671,6 +704,8 @@ namespace features::combat {
const auto use_weapon_speed = systems::g_convars.get( CONVAR( "sv_accelerate_use_weapon_speed"_hash ) );
const auto water_slow_cvar = systems::g_convars.get( CONVAR( "sv_water_slow_amount"_hash ) );
+ constexpr auto tick_interval{ 0.015625f };
+
const auto buttons = g::memory.read( movement_services + SCHEMA( "CPlayer_MovementServices", "m_nButtons"_hash ) );
const auto ducking_state = g::memory.read( movement_services + SCHEMA( "CCSPlayer_MovementServices", "m_bDucking"_hash ) );
@@ -706,7 +741,7 @@ namespace features::combat {
}
const auto control = std::fmaxf( spd, stopspeed_cvar );
- const auto drop = control * friction_cvar * surface_friction * player_friction * cstypes::tick_interval;
+ const auto drop = control * friction_cvar * surface_friction * player_friction * tick_interval;
const auto adjusted = std::fmaxf( spd - drop, 0.0f );
if ( adjusted < spd )
@@ -762,7 +797,7 @@ namespace features::combat {
}
}
- const auto gain = accel * cstypes::tick_interval * final_cap * surface_friction;
+ const auto gain = accel * tick_interval * final_cap * surface_friction;
const auto current_proj = vel.dot( dir );
return std::fminf( gain, std::fmaxf( 0.0f, -current_proj ) );
@@ -799,8 +834,8 @@ namespace features::combat {
sim_vel.x += wish_dir.x * accel_amount;
sim_vel.y += wish_dir.y * accel_amount;
- sim_pos.x += sim_vel.x * cstypes::tick_interval;
- sim_pos.y += sim_vel.y * cstypes::tick_interval;
+ sim_pos.x += sim_vel.x * tick_interval;
+ sim_pos.y += sim_vel.y * tick_interval;
}
return sim_pos;
@@ -861,7 +896,7 @@ namespace features::combat {
const auto latency = static_cast< float >( ping ) * 0.001f;
const auto interp_time = g::memory.read( pawn + 0x290 ); // client @ 48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC ? 49 63 D8 48 8B F1
- return latency * 0.5f + interp_time;
+ return latency + interp_time;
}
float shared::get_spread( std::uintptr_t weapon_vdata ) const
@@ -869,152 +904,179 @@ namespace features::combat {
return g::memory.read( weapon_vdata + SCHEMA( "CCSWeaponBaseVData", "m_flSpread"_hash ) );
}
- float shared::get_inaccuracy( std::uintptr_t pawn, std::uintptr_t weapon, std::uintptr_t weapon_vdata, const math::vector3& eye_angles ) const
+ float shared::get_base_inaccuracy(std::uintptr_t weapon, std::uintptr_t weapon_vdata, std::uintptr_t pawn) const
{
- const auto forcespread = systems::g_convars.get( CONVAR( "weapon_accuracy_forcespread"_hash ) );
- if ( forcespread > 0.0f )
- {
- return std::fminf( forcespread, 1.0f );
- }
+ const auto fire_mode = g::memory.read(weapon + SCHEMA("C_CSWeaponBase", "m_weaponMode"_hash));
+ const auto flags = g::memory.read(pawn + SCHEMA("C_BaseEntity", "m_fFlags"_hash));
+ const auto on_ground = (flags & 1) != 0;
+ const auto crouching = (flags & 2) != 0;
+ const auto move_type = g::memory.read(pawn + SCHEMA("C_BaseEntity", "m_MoveType"_hash));
+
+ const auto inaccuracy_crouch_pair = g::memory.read>(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flInaccuracyCrouch"_hash));
+ const auto inaccuracy_stand_pair = g::memory.read>(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flInaccuracyStand"_hash));
- const auto nospread = systems::g_convars.get( CONVAR( "weapon_accuracy_nospread"_hash ) );
- if ( nospread )
+ float base_inaccuracy{ 0.0f };
+
+ if (move_type == 9)
{
- return 0.0f;
+ const auto inaccuracy_ladder_pair = g::memory.read>(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flInaccuracyLadder"_hash));
+ base_inaccuracy = (fire_mode ? inaccuracy_stand_pair.second : inaccuracy_stand_pair.first)
+ + (fire_mode ? inaccuracy_ladder_pair.second : inaccuracy_ladder_pair.first);
}
+ else
+ {
+ const auto recoil_index = g::memory.read(weapon + SCHEMA("C_CSWeaponBase", "m_flRecoilIndex"_hash));
+ const auto weapon_type = g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_WeaponType"_hash));
- const auto fire_mode = g::memory.read( weapon + SCHEMA( "C_CSWeaponBase", "m_weaponMode"_hash ) );
- auto inaccuracy = g::memory.read( weapon + SCHEMA( "C_CSWeaponBase", "m_fAccuracyPenalty"_hash ) );
- const auto turning_inaccuracy = g::memory.read( weapon + SCHEMA( "C_CSWeaponBase", "m_flTurningInaccuracy"_hash ) );
+ float recovery_time{ 0.0f };
- const auto max_speed_pair = g::memory.read>( weapon_vdata + SCHEMA( "CCSWeaponBaseVData", "m_flMaxSpeed"_hash ) );
- const auto inaccuracy_move_pair = g::memory.read>( weapon_vdata + SCHEMA( "CCSWeaponBaseVData", "m_flInaccuracyMove"_hash ) );
- const auto inaccuracy_jump_initial = g::memory.read( weapon_vdata + SCHEMA( "CCSWeaponBaseVData", "m_flInaccuracyJumpInitial"_hash ) );
- const auto inaccuracy_jump_apex = g::memory.read( weapon_vdata + SCHEMA( "CCSWeaponBaseVData", "m_flInaccuracyJumpApex"_hash ) );
- const auto num_bullets = g::memory.read( weapon_vdata + SCHEMA( "CCSWeaponBaseVData", "m_nNumBullets"_hash ) );
+ if (weapon_type == 9) // sniper
+ {
+ recovery_time = g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flRecoveryTimeStand"_hash));
+ }
+ else if (!on_ground)
+ {
+ recovery_time = g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flRecoveryTimeCrouch"_hash)) * 4.0f;
+ }
+ else
+ {
+ const auto base_rec = crouching
+ ? g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flRecoveryTimeCrouch"_hash))
+ : g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flRecoveryTimeStand"_hash));
- const auto fm = [ & ]( const std::pair& p ) -> float { return fire_mode ? p.second : p.first; };
+ const auto final_rec = crouching
+ ? g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flRecoveryTimeCrouchFinal"_hash))
+ : g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flRecoveryTimeStandFinal"_hash));
- const auto max_speed = fm( max_speed_pair );
- const auto inaccuracy_move = fm( inaccuracy_move_pair );
+ if (final_rec == -1.0f)
+ {
+ recovery_time = base_rec;
+ }
+ else
+ {
+ const auto transition_start = g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_nRecoveryTransitionStartBullet"_hash));
+ const auto transition_end = g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_nRecoveryTransitionEndBullet"_hash));
- const auto player_velocity = g::memory.read( pawn + SCHEMA( "C_BaseEntity", "m_vecVelocity"_hash ) );
- const auto speed = player_velocity.length_2d( );
- const auto flags = g::memory.read( pawn + SCHEMA( "C_BaseEntity", "m_fFlags"_hash ) );
- const auto is_walking = g::memory.read( pawn + SCHEMA( "C_CSPlayerPawn", "m_bIsWalking"_hash ) );
- const auto on_ground = ( flags & 1 ) != 0;
+ if (recoil_index <= transition_start)
+ {
+ recovery_time = base_rec;
+ }
+ else if (recoil_index >= transition_end)
+ {
+ recovery_time = final_rec;
+ }
+ else
+ {
+ recovery_time = base_rec
+ + ((recoil_index - transition_start) / (transition_end - transition_start))
+ * (final_rec - base_rec);
+ }
+ }
+ }
- const auto edge0 = max_speed * 0.34f;
- const auto edge1 = max_speed * 0.95f;
+ base_inaccuracy = crouching
+ ? (fire_mode ? inaccuracy_crouch_pair.second : inaccuracy_crouch_pair.first)
+ : (fire_mode ? inaccuracy_stand_pair.second : inaccuracy_stand_pair.first);
+ }
- auto move_factor{ 0.0f };
+ return base_inaccuracy;
+ }
- if ( edge0 == edge1 )
+ float shared::get_inaccuracy(std::uintptr_t pawn, std::uintptr_t weapon, std::uintptr_t weapon_vdata, const math::vector3& eye_angles, float* out_move_inaccuracy, float* out_air_inaccuracy) const
+ {
+ if (!pawn || !weapon || !weapon_vdata)
{
- move_factor = ( speed - edge1 >= 0.0f ) ? 1.0f : 0.0f;
+ return 0.0f;
}
- else
+
+ const auto forcespread = systems::g_convars.get(CONVAR("weapon_accuracy_force_spread"_hash));
+ if (forcespread > 0.0f)
{
- move_factor = std::clamp( ( speed - edge0 ) / ( edge1 - edge0 ), 0.0f, 1.0f );
+ return std::fminf(forcespread, 1.0f);
}
- auto move_inaccuracy{ 0.0f };
-
- if ( move_factor > 0.0f )
+ const auto nospread = systems::g_convars.get(CONVAR("weapon_accuracy_nospread"_hash));
+ if (nospread)
{
- if ( !is_walking )
- {
- move_factor = std::powf( move_factor, 0.25f );
- }
-
- move_inaccuracy = move_factor * inaccuracy_move;
+ return 0.0f;
}
- auto air_inaccuracy{ 0.0f };
+ const auto fire_mode = g::memory.read(weapon + SCHEMA("C_CSWeaponBase", "m_weaponMode"_hash));
+ const auto inaccuracy_penalty = g::memory.read(weapon + SCHEMA("C_CSWeaponBase", "m_fAccuracyPenalty"_hash));
- if ( !on_ground )
- {
- const auto jump_impulse = systems::g_convars.get( CONVAR( "sv_jump_impulse"_hash ) );
- const auto sqrt_threshold = std::sqrtf( std::fabsf( jump_impulse ) );
- const auto sqrt_vertical = std::sqrtf( std::fabsf( player_velocity.z ) );
- const auto lo = sqrt_threshold * 0.25f;
+ const auto max_speed_pair = g::memory.read>(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flMaxSpeed"_hash));
+ const auto max_speed = fire_mode ? max_speed_pair.second : max_speed_pair.first;
- if ( lo == sqrt_threshold )
- {
- air_inaccuracy = ( sqrt_vertical - sqrt_threshold >= 0.0f ) ? inaccuracy_jump_initial : inaccuracy_jump_apex;
- }
- else
- {
- const auto frac = ( sqrt_vertical - lo ) / ( sqrt_threshold - lo );
- air_inaccuracy = inaccuracy_jump_apex + frac * ( inaccuracy_jump_initial - inaccuracy_jump_apex );
- }
+ const auto velocity = g::memory.read(pawn + SCHEMA("C_BaseEntity", "m_vecVelocity"_hash));
+ const auto speed = velocity.length_2d();
- if ( air_inaccuracy < 0.0f )
- {
- air_inaccuracy = 0.0f;
- }
- else
+ const auto inaccuracy_move_pair = g::memory.read>(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flInaccuracyMove"_hash));
+ const auto inaccuracy_move = fire_mode ? inaccuracy_move_pair.second : inaccuracy_move_pair.first;
+
+ const auto is_walking = g::memory.read(pawn + SCHEMA("C_CSPlayerPawn", "m_bIsWalking"_hash));
+
+ auto move_factor = features::combat::detail::remap_value(speed, max_speed * 0.34f, max_speed * 0.95f, 0.0f, 1.0f);
+ if (move_factor > 0.0f)
+ {
+ if (!is_walking)
{
- air_inaccuracy = std::fminf( inaccuracy_jump_initial * 2.0f, air_inaccuracy );
+ move_factor = std::powf(move_factor, 0.25f);
}
}
- return std::fminf( 1.0f, turning_inaccuracy + move_inaccuracy + air_inaccuracy + inaccuracy );
- }
+ const auto weapon_move_inaccuracy = move_factor * inaccuracy_move;
- bool shared::ray_hits_capsule( const math::vector3& ray_origin, const math::vector3& ray_dir, const math::vector3& capsule_start, const math::vector3& capsule_end, float radius ) const
- {
- const auto capsule_vec = capsule_end - capsule_start;
- const auto capsule_length = capsule_vec.length( );
+ if (out_move_inaccuracy)
+ *out_move_inaccuracy = weapon_move_inaccuracy;
- if ( capsule_length < 0.001f )
- {
- const auto to_center = capsule_start - ray_origin;
- const auto projection = to_center.dot( ray_dir );
+ auto total = inaccuracy_penalty + weapon_move_inaccuracy;
- if ( projection < 0.0f )
- {
- return false;
- }
+ const auto flags = g::memory.read(pawn + SCHEMA("C_BaseEntity", "m_fFlags"_hash));
+ const auto on_ground = (flags & 1) != 0;
- const auto closest = ray_origin + ray_dir * projection;
- return ( closest - capsule_start ).length_sqr( ) <= radius * radius;
- }
+ if (!on_ground)
+ {
+ const auto air_spread_scale = systems::g_convars.get(CONVAR("weapon_air_spread_scale"_hash));
+ const auto jump_initial = g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flInaccuracyJumpInitial"_hash)) * air_spread_scale;
+ const auto jump_apex = g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_flInaccuracyJumpApex"_hash)) * air_spread_scale;
- const auto capsule_dir = capsule_vec / capsule_length;
- const auto w = ray_origin - capsule_start;
+ const auto impulse = systems::g_convars.get(CONVAR("sv_jump_impulse"_hash));
+ const auto jump_vel = std::sqrtf(std::fabsf(impulse));
+ const auto cur_vel = std::sqrtf(std::fabsf(velocity.z));
+ const auto lo = jump_vel * 0.25f;
- const auto a = ray_dir.dot( ray_dir );
- const auto b = ray_dir.dot( capsule_dir );
- const auto c = capsule_dir.dot( capsule_dir );
- const auto d = ray_dir.dot( w );
- const auto e = capsule_dir.dot( w );
+ float air{ 0.0f };
+ if (lo == jump_vel) // only true when jump_vel == 0
+ {
+ air = (cur_vel >= jump_vel) ? jump_initial : jump_apex;
+ }
+ else
+ {
+ const auto frac = std::clamp((cur_vel - lo) / (jump_vel - lo), 0.0f, 1.0f);
+ air = jump_apex + frac * (jump_initial - jump_apex);
+ }
- const auto denom = a * c - b * b;
+ const auto air_inaccuracy = (air >= 0.0f) ? std::fminf(jump_initial * 2.0f, air) : 0.0f;
- float s, t;
+ if (out_air_inaccuracy)
+ *out_air_inaccuracy = air_inaccuracy;
- if ( std::abs( denom ) < 0.0001f )
- {
- s = 0.0f;
- t = ( b > c ? d / b : e / c );
+ total += air_inaccuracy;
}
else
{
- s = ( b * e - c * d ) / denom;
- t = ( a * e - b * d ) / denom;
+ if (out_air_inaccuracy)
+ *out_air_inaccuracy = 0.0f;
}
- t = std::clamp( t, 0.0f, capsule_length );
- if ( s < 0.0f )
+ const auto num_bullets = g::memory.read(weapon_vdata + SCHEMA("CCSWeaponBaseVData", "m_nNumBullets"_hash));
+ const auto shotgun_patterns = systems::g_convars.get(CONVAR("weapon_accuracy_shotgun_spread_patterns"_hash));
+ if (shotgun_patterns && num_bullets > 1)
{
- return false;
+ total += 0.0f;
}
- const auto point_on_capsule = capsule_start + capsule_dir * t;
- const auto point_on_ray = ray_origin + ray_dir * s;
-
- return ( point_on_ray - point_on_capsule ).length_sqr( ) <= radius * radius;
+ return std::fminf(1.0f, get_base_inaccuracy(weapon, weapon_vdata, pawn) + total);
}
} // namespace features::combat
\ No newline at end of file
diff --git a/catalyst/project/core/features/impl/esp/footsteps.cpp b/catalyst/project/core/features/impl/esp/footsteps.cpp
new file mode 100644
index 0000000..fc65075
--- /dev/null
+++ b/catalyst/project/core/features/impl/esp/footsteps.cpp
@@ -0,0 +1,148 @@
+#include
+
+namespace features::esp {
+
+ void footsteps::tick( )
+ {
+ const auto& cfg = settings::g_esp.m_player.m_footsteps;
+ if ( !cfg.enabled )
+ {
+ this->m_rings.clear( );
+ return;
+ }
+
+ const auto global_vars = g::memory.read( g::offsets.global_vars );
+ if ( !global_vars ) return;
+
+ const float current_time = g::memory.read( global_vars + 0x30 );
+ const auto local_pawn = systems::g_local.pawn( );
+
+ for ( const auto& player : systems::g_collector.players( ) )
+ {
+ if ( !player.pawn || !player.alive )
+ continue;
+
+ if ( !systems::g_local.is_enemy( player.team ) && !cfg.show_teammates )
+ continue;
+
+ if ( player.pawn == local_pawn )
+ continue;
+
+ const std::uint32_t flags = g::memory.read( player.pawn + SCHEMA( "C_BaseEntity", "m_fFlags"_hash ) );
+ const bool is_on_ground = ( flags & ( 1 << 0 ) ) != 0;
+
+ bool was_on_ground = true;
+ if ( auto it = m_prev_on_ground.find( player.pawn ); it != m_prev_on_ground.end( ) )
+ {
+ was_on_ground = it->second;
+ }
+
+ const float speed = player.velocity.length( );
+ const math::vector3 feet_pos = player.origin;
+
+ if ( !was_on_ground && is_on_ground && speed > 80.0f )
+ {
+ ring r;
+ r.world_pos = feet_pos;
+ float land_radius = cfg.land_max_radius * std::min( 1.0f, speed / 400.0f );
+ r.max_radius = std::max( cfg.land_max_radius * 0.5f, land_radius );
+ r.color = cfg.land_color;
+ r.start_time = current_time;
+ m_rings.push_back( r );
+ }
+ else if ( was_on_ground && !is_on_ground && speed > 50.0f )
+ {
+ ring r;
+ r.world_pos = feet_pos;
+ r.max_radius = cfg.jump_max_radius;
+ r.color = cfg.jump_color;
+ r.start_time = current_time;
+ m_rings.push_back( r );
+ }
+ else if ( was_on_ground && is_on_ground && speed > 50.0f )
+ {
+ float last_time = 0;
+ if ( auto it = m_last_step_time.find( player.pawn ); it != m_last_step_time.end( ) )
+ {
+ last_time = it->second;
+ }
+
+ if ( current_time - last_time > 0.25f )
+ {
+ m_last_step_time[ player.pawn ] = current_time;
+ ring r;
+ r.world_pos = feet_pos;
+ float step_radius = cfg.footstep_max_radius * std::min( 1.0f, speed / 250.0f );
+ r.max_radius = std::max( cfg.footstep_max_radius * 0.4f, step_radius );
+ r.color = cfg.footstep_color;
+ r.start_time = current_time;
+ m_rings.push_back( r );
+ }
+ }
+
+ m_prev_on_ground[ player.pawn ] = is_on_ground;
+ }
+
+ const float max_age = cfg.expand_duration + cfg.fade_duration;
+ m_rings.erase(
+ std::remove_if( m_rings.begin( ), m_rings.end( ),
+ [ current_time, max_age ]( const ring& r ) { return ( current_time - r.start_time ) > max_age; } ),
+ m_rings.end( )
+ );
+ }
+
+ void footsteps::on_render( zdraw::draw_list& draw_list )
+ {
+ const auto& cfg = settings::g_esp.m_player.m_footsteps;
+ if ( !cfg.enabled ) return;
+
+ const auto global_vars = g::memory.read( g::offsets.global_vars );
+ if ( !global_vars ) return;
+
+ const float current_time = g::memory.read( global_vars + 0x30 );
+
+ for ( const auto& r : m_rings )
+ {
+ float elapsed = current_time - r.start_time;
+ if ( elapsed < 0.0f || elapsed > ( cfg.expand_duration + cfg.fade_duration ) )
+ continue;
+
+ float current_radius;
+ float alpha_factor;
+
+ if ( elapsed < cfg.expand_duration )
+ {
+ float t = elapsed / cfg.expand_duration;
+ current_radius = r.max_radius * t;
+ alpha_factor = 1.0f;
+ }
+ else
+ {
+ current_radius = r.max_radius;
+ float fade_t = ( elapsed - cfg.expand_duration ) / cfg.fade_duration;
+ alpha_factor = 1.0f - fade_t;
+ }
+
+ if ( alpha_factor < 0.01f ) continue;
+
+ const auto screen_pos = systems::g_view.project( r.world_pos );
+ if ( !systems::g_view.projection_valid( screen_pos ) )
+ continue;
+
+ const auto edge_world = r.world_pos + math::vector3{ current_radius, 0, 0 };
+ const auto edge_screen = systems::g_view.project( edge_world );
+
+ if ( systems::g_view.projection_valid( edge_screen ) )
+ {
+ float screen_radius = std::abs( edge_screen.x - screen_pos.x );
+ if ( screen_radius < 1.0f ) screen_radius = 1.0f;
+
+ zdraw::rgba color = r.color;
+ color.a = static_cast< std::uint8_t >( color.a * alpha_factor );
+
+ draw_list.add_circle( screen_pos.x, screen_pos.y, screen_radius, color, cfg.segments, cfg.thickness );
+ }
+ }
+ }
+
+} // namespace features::esp
diff --git a/catalyst/project/core/features/impl/esp/item.cpp b/catalyst/project/core/features/impl/esp/item.cpp
index 19e24d5..2e30d46 100644
--- a/catalyst/project/core/features/impl/esp/item.cpp
+++ b/catalyst/project/core/features/impl/esp/item.cpp
@@ -59,7 +59,7 @@ namespace features::esp {
const auto x = std::floorf( screen.x - text_w * 0.5f );
const auto y = std::floorf( screen.y - text_h * 0.5f + y_offset );
- draw_list.add_text( x, y, icon, nullptr, cfg.color, zdraw::text_style::outlined );
+ draw_list.add_text( x, y, icon, zdraw::get_font( ), cfg.color, zdraw::text_style::outlined );
zdraw::pop_font( );
@@ -75,7 +75,7 @@ namespace features::esp {
const auto x = std::floorf( screen.x - text_w * 0.5f );
const auto y = std::floorf( screen.y + y_offset );
- draw_list.add_text( x, y, name, nullptr, cfg.color, zdraw::text_style::outlined );
+ draw_list.add_text( x, y, name, zdraw::get_font( ), cfg.color, zdraw::text_style::outlined );
zdraw::pop_font( );
@@ -94,7 +94,7 @@ namespace features::esp {
const auto fraction = static_cast< float >( std::clamp( item.ammo, 0, item.max_ammo ) ) / item.max_ammo;
const auto color = fraction > 0.0f ? cfg.color : cfg.empty_color;
- draw_list.add_text( x, y, text, nullptr, color, zdraw::text_style::outlined );
+ draw_list.add_text( x, y, text, zdraw::get_font( ), color, zdraw::text_style::outlined );
zdraw::pop_font( );
diff --git a/catalyst/project/core/features/impl/esp/player.cpp b/catalyst/project/core/features/impl/esp/player.cpp
index 11c4946..5abede9 100644
--- a/catalyst/project/core/features/impl/esp/player.cpp
+++ b/catalyst/project/core/features/impl/esp/player.cpp
@@ -1,9 +1,11 @@
-#include
+#include
namespace features::esp {
void player::on_render( zdraw::draw_list& draw_list )
{
+ this->draw_movement_trails( draw_list );
+
const auto& cfg = settings::g_esp.m_player;
if ( !cfg.enabled )
{
@@ -20,12 +22,12 @@ namespace features::esp {
for ( const auto& player : systems::g_collector.players( ) )
{
- if ( !systems::g_local.is_enemy( player.team ) )
+ if ( !systems::g_local.is_enemy( player.team ) || !player.alive )
{
continue;
}
- const auto bones = systems::g_bones.get( player.bone_cache );
+ const auto& bones = player.bones;
if ( !bones.is_valid( ) )
{
continue;
@@ -78,9 +80,67 @@ namespace features::esp {
{
this->add_flags( draw_list, bounds, player, cfg.m_info_flags, offsets );
}
+
+ if ( cfg.m_oof_arrow.enabled )
+ {
+ this->add_oof_arrow( draw_list, player, cfg.m_oof_arrow );
+ }
}
}
+ void player::add_oof_arrow( zdraw::draw_list& draw_list, const systems::collector::player& player, const settings::esp::player::oof_arrow& cfg )
+ {
+ const auto screen_pos = systems::g_view.project( player.origin );
+ const auto [sw, sh] = zdraw::get_display_size( );
+
+ if ( systems::g_view.projection_valid( screen_pos ) )
+ {
+
+ }
+
+ const auto cx = sw * 0.5f;
+ const auto cy = sh * 0.5f;
+
+ const auto origin = systems::g_view.origin( );
+ const auto view_angles = systems::g_view.angles( );
+
+ // Calculate 2D direction in world space (ignoring Z for flat circular rotation)
+ const auto angle_to_target = math::helpers::calculate_angle( origin, player.origin );
+
+ // Get delta yaw and normalize
+ const auto yaw_delta = math::helpers::normalize_yaw( view_angles.y - angle_to_target.y );
+
+ const auto rad = math::helpers::deg_to_rad( yaw_delta - 90.0f );
+
+ const auto radius = cfg.radius;
+ const auto size = cfg.size;
+
+ const auto arrow_center_x = cx + std::cosf( rad ) * radius;
+ const auto arrow_center_y = cy + std::sinf( rad ) * radius;
+
+ // Tip points further away from center
+ const auto tip_x = arrow_center_x + std::cosf( rad ) * size;
+ const auto tip_y = arrow_center_y + std::sinf( rad ) * size;
+
+ // Sides are offset by 90 degrees
+ const auto side_angle_l = rad + std::numbers::pi_v * 0.5f;
+ const auto side_angle_r = rad - std::numbers::pi_v * 0.5f;
+ const auto side_w = size * 0.5f;
+
+ const auto left_x = arrow_center_x + std::cosf( side_angle_l ) * side_w;
+ const auto left_y = arrow_center_y + std::sinf( side_angle_l ) * side_w;
+
+ const auto right_x = arrow_center_x + std::cosf( side_angle_r ) * side_w;
+ const auto right_y = arrow_center_y + std::sinf( side_angle_r ) * side_w;
+
+ if ( cfg.outline )
+ {
+ draw_list.add_triangle( tip_x, tip_y, left_x, left_y, right_x, right_y, zdraw::rgba( 10, 10, 10, cfg.color.a ), 1.5f );
+ }
+
+ draw_list.add_triangle_filled( tip_x, tip_y, left_x, left_y, right_x, right_y, cfg.color );
+ }
+
void player::add_box( zdraw::draw_list& draw_list, const systems::bounds::data& bounds, const settings::esp::player::box& cfg, bool is_visible )
{
const auto& color = is_visible ? cfg.visible_color : cfg.occluded_color;
@@ -114,7 +174,7 @@ namespace features::esp {
draw_list.add_rect_filled_multi_color( x + 1, mid_y, w - 2, y + h - mid_y - 1, center_color, center_color, edge_color, edge_color );
}
- if ( cfg.style == settings::esp::player::box::style_type::full )
+ if ( cfg.style == (int)settings::esp::player::box::style0::full )
{
if ( cfg.outline )
{
@@ -142,67 +202,169 @@ namespace features::esp {
{
const auto& color = is_visible ? cfg.visible_color : cfg.occluded_color;
- constexpr std::array, 18> connections
- { {
- { 6, 5 },
- { 5, 4 },
- { 4, 3 },
- { 3, 2 },
- { 2, 1 },
- { 1, 0 },
- { 4, 8 },
- { 8, 9 },
- { 9, 10 },
- { 4, 13 },
- { 13, 14 },
- { 14, 15 },
- { 0, 22 },
- { 22, 23 },
- { 23, 24 },
- { 0, 25 },
- { 25, 26 },
- { 26, 27 },
- } };
-
- for ( const auto& [from, to] : connections )
- {
- const auto from_screen = systems::g_view.project( bones.get_position( from ) );
- const auto to_screen = systems::g_view.project( bones.get_position( to ) );
-
- if ( !systems::g_view.projection_valid( from_screen ) || !systems::g_view.projection_valid( to_screen ) )
+ if ( cfg.rounded )
+ {
+ static const std::array, 5> paths
+ { {
+ { 6, 5, 4, 3, 2, 1, 0 },
+ { 4, 8, 9, 10 },
+ { 4, 13, 14, 15 },
+ { 0, 22, 23, 24 },
+ { 0, 25, 26, 27 }
+ } };
+
+ for ( const auto& path : paths )
{
- continue;
+ std::vector points;
+
+ for ( const auto& bone_idx : path )
+ {
+ const auto screen = systems::g_view.project( bones.get_position( bone_idx ) );
+ if ( !systems::g_view.projection_valid( screen ) )
+ {
+ continue;
+ }
+
+ points.push_back( { screen.x, screen.y } );
+ }
+
+ if ( points.size( ) < 2 )
+ {
+ continue;
+ }
+
+ points.insert( points.begin( ), points.front( ) );
+ points.push_back( points.back( ) );
+
+ math::vector2 last{ 0.0f, 0.0f };
+ bool first = true;
+
+ for ( std::size_t i = 0; i + 3 < points.size( ); i++ )
+ {
+ const auto& p0 = points[ i ];
+ const auto& p1 = points[ i + 1 ];
+ const auto& p2 = points[ i + 2 ];
+ const auto& p3 = points[ i + 3 ];
+
+ for ( float t = 0.0f; t <= 1.0f; t += 0.1f )
+ {
+ const auto pt = math::helpers::catmull_rom( p0, p1, p2, p3, t );
+
+ if ( first )
+ {
+ last = pt;
+ first = false;
+ continue;
+ }
+
+ draw_list.add_line( last.x, last.y, pt.x, pt.y, color, cfg.thickness );
+ last = pt;
+ }
+ }
}
+ }
+ else
+ {
+ constexpr std::array, 18> connections
+ { {
+ { 6, 5 },
+ { 5, 4 },
+ { 4, 3 },
+ { 3, 2 },
+ { 2, 1 },
+ { 1, 0 },
+ { 4, 8 },
+ { 8, 9 },
+ { 9, 10 },
+ { 4, 13 },
+ { 13, 14 },
+ { 14, 15 },
+ { 0, 22 },
+ { 22, 23 },
+ { 23, 24 },
+ { 0, 25 },
+ { 25, 26 },
+ { 26, 27 },
+ } };
+
+ for ( const auto& [from, to] : connections )
+ {
+ const auto from_screen = systems::g_view.project( bones.get_position( from ) );
+ const auto to_screen = systems::g_view.project( bones.get_position( to ) );
- draw_list.add_line( from_screen.x, from_screen.y, to_screen.x, to_screen.y, color, cfg.thickness );
+ if ( !systems::g_view.projection_valid( from_screen ) || !systems::g_view.projection_valid( to_screen ) )
+ {
+ continue;
+ }
+
+ draw_list.add_line( from_screen.x, from_screen.y, to_screen.x, to_screen.y, color, cfg.thickness );
+ }
}
}
void player::add_hitboxes( zdraw::draw_list& draw_list, const systems::bones::data& bones, const systems::collector::player& player, const settings::esp::player::hitboxes& cfg, float current_time )
{
- const auto& color = player.is_visible ? cfg.visible_color : cfg.occluded_color;
- const auto eye_pos = systems::g_view.origin( );
-
auto& anim = this->m_animations[ player.controller ];
+ if ( player.health < anim.last_health ) anim.last_hit_time = current_time;
+ anim.last_health = player.health;
+
+ const auto hp = std::clamp( static_cast< float >( player.health ) / 100.0f, 0.0f, 1.0f );
+ const auto flash_t = std::clamp( ( current_time - anim.last_hit_time ) / 0.5f, 0.0f, 1.0f );
+ const auto red = zdraw::rgba{ 220, 40, 40, 255 };
- if ( player.health < anim.last_health )
+ auto color = player.is_visible ? cfg.visible_color : cfg.occluded_color;
+ auto outline_color = cfg.outline_color;
+
+ if ( cfg.mode == settings::esp::player::hitboxes::material::pulse )
{
- anim.last_damage_time = current_time;
+ const auto factor = ( std::sin( current_time * 5.0f ) * 0.5f + 0.5f );
+ color.a = static_cast< std::uint8_t >( (float)color.a * factor );
+ outline_color.a = static_cast< std::uint8_t >( (float)outline_color.a * factor );
}
- anim.last_health = player.health;
+ const auto eye_pos = systems::g_view.origin( );
- const auto hp = std::clamp( player.health / 100.0f, 0.0f, 1.0f );
- const auto flash_t = std::clamp( 1.0f - ( current_time - anim.last_damage_time ) * 2.5f, 0.0f, 1.0f );
+ if ( cfg.mode == settings::esp::player::hitboxes::material::wireframe )
+ {
+ float min_y{ 1e12f }, max_y{ -1e12f };
+ for ( const auto& hbox : player.hitboxes )
+ {
+ const auto bone_id = static_cast< std::uint32_t >( hbox.bone );
+ const auto position = bones.get_position( bone_id );
+ const auto rotation = bones.get_rotation( bone_id );
+
+ const auto top = rotation.rotate_vector( hbox.maxs ) + position;
+ const auto bottom = rotation.rotate_vector( hbox.mins ) + position;
+
+ const auto s_top = systems::g_view.project( top );
+ const auto s_bottom = systems::g_view.project( bottom );
+
+ if ( systems::g_view.projection_valid( s_top ) ) { min_y = std::min( min_y, s_top.y ); max_y = std::max( max_y, s_top.y ); }
+ if ( systems::g_view.projection_valid( s_bottom ) ) { min_y = std::min( min_y, s_bottom.y ); max_y = std::max( max_y, s_bottom.y ); }
+ }
- std::vector> pills;
+ const auto range = max_y - min_y;
+ const auto fill_line = min_y + range * hp;
- for ( const auto& hb : player.hitboxes )
- {
- if ( hb.index < 0 || hb.bone < 0 || hb.radius <= 0.0f )
+ for ( const auto& hbox : player.hitboxes )
{
- continue;
+ const auto bone_id = static_cast< std::uint32_t >( hbox.bone );
+ const auto position = bones.get_position( bone_id );
+ const auto rotation = bones.get_rotation( bone_id );
+
+ this->add_capsule( draw_list, hbox.maxs, hbox.mins, hbox.radius, rotation, position, color, 10, false, 0.0f );
+ if ( cfg.health_indicator && hp < 1.0f )
+ {
+ this->add_capsule( draw_list, hbox.maxs, hbox.mins, hbox.radius, rotation, position, red, 10, true, fill_line );
+ }
}
+ return;
+ }
+
+ std::vector> pills;
+ for ( const auto& hb : player.hitboxes )
+ {
+ if ( hb.index < 0 || hb.bone < 0 || hb.radius <= 0.0f ) continue;
const auto& bone = bones.bones[ hb.bone ];
const auto center_local = ( hb.mins + hb.maxs ) * 0.5f;
@@ -212,18 +374,9 @@ namespace features::esp {
const auto longest = std::max( { std::abs( half_extent.x ), std::abs( half_extent.y ), std::abs( half_extent.z ) } );
math::vector3 axis_local{};
- if ( std::abs( half_extent.x ) >= std::abs( half_extent.y ) && std::abs( half_extent.x ) >= std::abs( half_extent.z ) )
- {
- axis_local = { longest, 0.0f, 0.0f };
- }
- else if ( std::abs( half_extent.y ) >= std::abs( half_extent.z ) )
- {
- axis_local = { 0.0f, longest, 0.0f };
- }
- else
- {
- axis_local = { 0.0f, 0.0f, longest };
- }
+ if ( std::abs( half_extent.x ) >= std::abs( half_extent.y ) && std::abs( half_extent.x ) >= std::abs( half_extent.z ) ) axis_local = { longest, 0.0f, 0.0f };
+ else if ( std::abs( half_extent.y ) >= std::abs( half_extent.z ) ) axis_local = { 0.0f, longest, 0.0f };
+ else axis_local = { 0.0f, 0.0f, longest };
const auto axis_world = math::helpers::rotate_by_quat( bone.rotation, axis_local );
const auto cap_a = center_world - axis_world;
@@ -232,10 +385,7 @@ namespace features::esp {
const auto sa = systems::g_view.project( cap_a );
const auto sb = systems::g_view.project( cap_b );
- if ( !systems::g_view.projection_valid( sa ) || !systems::g_view.projection_valid( sb ) )
- {
- continue;
- }
+ if ( !systems::g_view.projection_valid( sa ) || !systems::g_view.projection_valid( sb ) ) continue;
const auto view_dir = ( center_world - eye_pos ).normalized( );
auto perp = axis_world.cross( view_dir );
@@ -247,126 +397,65 @@ namespace features::esp {
const auto pl2 = perp.length( );
perp = pl2 > 0.001f ? perp / pl2 : math::vector3{ 1.0f, 0.0f, 0.0f };
}
- else
- {
- perp = perp / pl;
- }
+ else perp = perp / pl;
const auto perp_world = perp * hb.radius;
const auto p_left = systems::g_view.project( center_world + perp_world );
const auto p_right = systems::g_view.project( center_world - perp_world );
- if ( !systems::g_view.projection_valid( p_left ) || !systems::g_view.projection_valid( p_right ) )
- {
- continue;
- }
+ if ( !systems::g_view.projection_valid( p_left ) || !systems::g_view.projection_valid( p_right ) ) continue;
- const auto screen_radius = std::sqrt( ( p_left.x - p_right.x ) * ( p_left.x - p_right.x ) + ( p_left.y - p_right.y ) * ( p_left.y - p_right.y ) ) * 0.5f;
- if ( screen_radius <= 0.0f || screen_radius > 800.0f )
- {
- continue;
- }
+ const auto screen_radius = std::sqrt( std::powf( p_left.x - p_right.x, 2.0f ) + std::powf( p_left.y - p_right.y, 2.0f ) ) * 0.5f;
+ if ( screen_radius <= 0.0f || screen_radius > 800.0f ) continue;
pills.push_back( poly2d::make_pill( { sa.x, sa.y }, { sb.x, sb.y }, screen_radius, 4 ) );
}
- if ( pills.empty( ) )
- {
- return;
- }
-
+ if ( pills.empty( ) ) return;
auto merged = poly2d::union_pills( pills );
for ( const auto& outline : merged.outlines )
{
- if ( outline.size( ) < 3 )
- {
- continue;
- }
+ if ( outline.size( ) < 3 ) continue;
+
+ float min_y = outline[ 0 ].y, max_y = outline[ 0 ].y;
+ for ( const auto& p : outline ) { min_y = std::min( min_y, p.y ); max_y = std::max( max_y, p.y ); }
+
+ const auto range = max_y - min_y;
+ const auto fill_line = min_y + range * hp;
if ( cfg.fill )
{
- auto min_y = outline[ 0 ].y, max_y = outline[ 0 ].y;
- for ( const auto& p : outline )
+ const auto tris = poly2d::triangulate( outline );
+ for ( std::size_t i = 0; i + 5 < tris.size( ); i += 6 )
{
- min_y = std::min( min_y, p.y );
- max_y = std::max( max_y, p.y );
+ draw_list.add_triangle_filled( tris[ i ], tris[ i + 1 ], tris[ i + 2 ], tris[ i + 3 ], tris[ i + 4 ], tris[ i + 5 ], color );
}
- const auto range = max_y - min_y;
-
- if ( hp < 1.0f || flash_t > 0.0f )
+ if ( cfg.health_indicator && hp < 1.0f )
{
- const auto base_alpha{ 80 };
- const auto flash_alpha = static_cast< std::uint8_t >( base_alpha + ( 200 - base_alpha ) * flash_t );
- const auto fill_line = min_y + range * hp;
- const auto red = zdraw::rgba{ 220, 40, 40, flash_alpha };
- const auto tris = poly2d::triangulate( outline );
-
- std::vector clipped;
- clipped.reserve( tris.size( ) * 2 );
-
- auto clip_triangle_above = [ & ]( float x0, float y0, float x1, float y1, float x2, float y2 )
- {
- const auto a0 = y0 >= fill_line;
- const auto a1 = y1 >= fill_line;
- const auto a2 = y2 >= fill_line;
- const auto count = static_cast< int >( a0 ) + static_cast< int >( a1 ) + static_cast< int >( a2 );
-
- if ( count == 0 )
- {
- return;
- }
-
- if ( count == 3 )
- {
- clipped.push_back( x0 ); clipped.push_back( y0 );
- clipped.push_back( x1 ); clipped.push_back( y1 );
- clipped.push_back( x2 ); clipped.push_back( y2 );
- return;
- }
-
- auto lerp_x = [ ]( float ax, float ay, float bx, float by, float cy ) { return ax + ( bx - ax ) * ( ( cy - ay ) / ( by - ay ) ); };
-
- if ( count == 1 )
- {
- float tx, ty, ix0, ix1;
- if ( a0 ) { tx = x0; ty = y0; ix0 = lerp_x( x0, y0, x1, y1, fill_line ); ix1 = lerp_x( x0, y0, x2, y2, fill_line ); }
- else if ( a1 ) { tx = x1; ty = y1; ix0 = lerp_x( x1, y1, x0, y0, fill_line ); ix1 = lerp_x( x1, y1, x2, y2, fill_line ); }
- else { tx = x2; ty = y2; ix0 = lerp_x( x2, y2, x0, y0, fill_line ); ix1 = lerp_x( x2, y2, x1, y1, fill_line ); }
-
- clipped.push_back( tx ); clipped.push_back( ty );
- clipped.push_back( ix0 ); clipped.push_back( fill_line );
- clipped.push_back( ix1 ); clipped.push_back( fill_line );
- }
- else
- {
- float bx, by, kx0, ky0, kx1, ky1;
- if ( !a0 ) { bx = x0; by = y0; kx0 = x1; ky0 = y1; kx1 = x2; ky1 = y2; }
- else if ( !a1 ) { bx = x1; by = y1; kx0 = x0; ky0 = y0; kx1 = x2; ky1 = y2; }
- else { bx = x2; by = y2; kx0 = x0; ky0 = y0; kx1 = x1; ky1 = y1; }
-
- const auto ix0 = lerp_x( bx, by, kx0, ky0, fill_line );
- const auto ix1 = lerp_x( bx, by, kx1, ky1, fill_line );
-
- clipped.push_back( kx0 ); clipped.push_back( ky0 );
- clipped.push_back( kx1 ); clipped.push_back( ky1 );
- clipped.push_back( ix0 ); clipped.push_back( fill_line );
-
- clipped.push_back( kx1 ); clipped.push_back( ky1 );
- clipped.push_back( ix0 ); clipped.push_back( fill_line );
- clipped.push_back( ix1 ); clipped.push_back( fill_line );
- }
- };
-
- for ( std::size_t i = 0; i + 5 < tris.size( ); i += 6 )
+ std::vector clipped;
+ for ( std::size_t i = 0; i < outline.size( ); ++i )
{
- clip_triangle_above( tris[ i ], tris[ i + 1 ], tris[ i + 2 ], tris[ i + 3 ], tris[ i + 4 ], tris[ i + 5 ] );
+ const auto& p1 = outline[ i ];
+ const auto& p2 = outline[ ( i + 1 ) % outline.size( ) ];
+ const bool p1_in = ( p1.y >= fill_line );
+ const bool p2_in = ( p2.y >= fill_line );
+ if ( p1_in ) clipped.push_back( p1 );
+ if ( p1_in != p2_in )
+ {
+ const float t = ( fill_line - p1.y ) / ( p2.y - p1.y );
+ clipped.push_back( { p1.x + t * ( p2.x - p1.x ), fill_line } );
+ }
}
-
- for ( std::size_t i = 0; i + 5 < clipped.size( ); i += 6 )
+
+ if ( clipped.size( ) >= 3 )
{
- draw_list.add_triangle_filled( clipped[ i ], clipped[ i + 1 ], clipped[ i + 2 ], clipped[ i + 3 ], clipped[ i + 4 ], clipped[ i + 5 ], red );
+ const auto clipped_tris = poly2d::triangulate( clipped );
+ for ( std::size_t i = 0; i + 5 < clipped_tris.size( ); i += 6 )
+ {
+ draw_list.add_triangle_filled( clipped_tris[ i ], clipped_tris[ i + 1 ], clipped_tris[ i + 2 ], clipped_tris[ i + 3 ], clipped_tris[ i + 4 ], clipped_tris[ i + 5 ], red );
+ }
}
}
}
@@ -375,14 +464,51 @@ namespace features::esp {
{
std::vector flat;
flat.reserve( outline.size( ) * 2 );
+ for ( const auto& p : outline ) { flat.push_back( p.x ); flat.push_back( p.y ); }
- for ( const auto& p : outline )
+ if ( cfg.mode == settings::esp::player::hitboxes::material::glow )
{
- flat.push_back( p.x );
- flat.push_back( p.y );
+ for ( int i = 1; i <= 6; ++i )
+ {
+ const auto glow_alpha = static_cast< std::uint8_t >( ( 1.0f - ( float )i / 7.0f ) * 120.0f );
+ const auto alpha = static_cast< std::uint8_t >( ( float)glow_alpha * ( (float)outline_color.a / 255.0f ) );
+ draw_list.add_polyline( std::span( flat.data( ), flat.size( ) ), zdraw::rgba{ outline_color.r, outline_color.g, outline_color.b, alpha }, true, 1.0f + ( float )i * 2.0f );
+ }
}
- draw_list.add_polyline( std::span( flat.data( ), flat.size( ) ), color, true );
+ draw_list.add_polyline( std::span( flat.data( ), flat.size( ) ), outline_color, true, 2.0f );
+
+ if ( cfg.health_indicator && hp < 1.0f )
+ {
+ auto draw_clipped_outline = [&]( float y_threshold, const zdraw::rgba& col, float thickness )
+ {
+ for ( std::size_t i = 0; i < outline.size( ); ++i )
+ {
+ const auto& p1 = outline[ i ];
+ const auto& p2 = outline[ ( i + 1 ) % outline.size( ) ];
+
+ if ( p1.y < y_threshold && p2.y < y_threshold ) continue;
+ if ( p1.y >= y_threshold && p2.y >= y_threshold ) draw_list.add_line( p1.x, p1.y, p2.x, p2.y, col, thickness );
+ else
+ {
+ const float t = ( y_threshold - p1.y ) / ( p2.y - p1.y );
+ const math::vector2 intersection{ p1.x + t * ( p2.x - p1.x ), y_threshold };
+ if ( p1.y >= y_threshold ) draw_list.add_line( p1.x, p1.y, intersection.x, intersection.y, col, thickness );
+ else draw_list.add_line( intersection.x, intersection.y, p2.x, p2.y, col, thickness );
+ }
+ }
+ };
+
+ if ( cfg.mode == settings::esp::player::hitboxes::material::glow )
+ {
+ for ( int i = 1; i <= 4; ++i )
+ {
+ const auto alpha = static_cast< std::uint8_t >( ( 1.0f - ( float )i / 5.0f ) * 160.0f );
+ draw_clipped_outline( fill_line, zdraw::rgba{ red.r, red.g, red.b, alpha }, 1.0f + ( float )i * 2.5f );
+ }
+ }
+ draw_clipped_outline( fill_line, red, 2.0f );
+ }
}
}
}
@@ -393,9 +519,10 @@ namespace features::esp {
constexpr auto bar_size{ 3.5f };
constexpr auto padding{ 4.0f };
+ constexpr auto outline_size{ 1.0f };
const auto clamped_health = std::clamp( player.health, 0, 100 );
- const auto target_fraction = clamped_health / 100.0f;
+ const auto target_fraction = static_cast< float >( clamped_health ) / 100.0f;
if ( !anim.initialized || ( target_fraction - anim.health.value( ) > 0.5f ) )
{
@@ -409,44 +536,40 @@ namespace features::esp {
}
const auto fraction = anim.health.value( );
- const auto outline_size = cfg.outline ? 1.0f : 0.0f;
- const auto vertical = cfg.position == settings::esp::player::health_bar::position_type::left;
+ const auto vertical = ( cfg.position == (int)settings::esp::player::health_bar::position::left );
const auto bar_w = vertical ? bar_size : std::floorf( bounds.width( ) );
const auto bar_h = vertical ? std::floorf( bounds.height( ) ) : bar_size;
const auto filled = std::floorf( ( vertical ? bar_h : bar_w ) * fraction );
- const auto x = [ & ]( )
- {
- if ( cfg.position == settings::esp::player::health_bar::position_type::left )
- {
- return std::floorf( bounds.min.x - bar_size - padding - offsets.left - outline_size );
- }
+ float x{}, y{};
- return std::floorf( bounds.min.x );
- }( );
-
- const auto y = [ & ]( )
- {
- switch ( cfg.position )
- {
- case settings::esp::player::health_bar::position_type::left: return std::floorf( bounds.min.y );
- case settings::esp::player::health_bar::position_type::top: return std::floorf( bounds.min.y - bar_size - padding - offsets.top - outline_size );
- case settings::esp::player::health_bar::position_type::bottom: return std::floorf( bounds.max.y + padding + offsets.bottom + outline_size );
- }
- return 0.0f;
- }( );
+ switch ( cfg.position )
+ {
+ case (int)settings::esp::player::health_bar::position::left:
+ x = std::floorf( bounds.min.x - bar_size - padding - offsets.left - outline_size );
+ y = std::floorf( bounds.min.y );
+ break;
+ case (int)settings::esp::player::health_bar::position::top:
+ x = std::floorf( bounds.min.x );
+ y = std::floorf( bounds.min.y - bar_size - padding - offsets.top - outline_size );
+ break;
+ case (int)settings::esp::player::health_bar::position::bottom:
+ x = std::floorf( bounds.min.x );
+ y = std::floorf( bounds.max.y + padding + offsets.bottom + outline_size );
+ break;
+ }
switch ( cfg.position )
{
- case settings::esp::player::health_bar::position_type::left: offsets.left += bar_size + padding + ( outline_size * 2.0f ); break;
- case settings::esp::player::health_bar::position_type::top: offsets.top += bar_size + padding + ( outline_size * 2.0f ); break;
- case settings::esp::player::health_bar::position_type::bottom: offsets.bottom += bar_size + padding + ( outline_size * 2.0f ); break;
+ case (int)settings::esp::player::health_bar::position::left: offsets.left += bar_size + padding + ( outline_size * 2.0f ); break;
+ case (int)settings::esp::player::health_bar::position::top: offsets.top += bar_size + padding + ( outline_size * 2.0f ); break;
+ case (int)settings::esp::player::health_bar::position::bottom: offsets.bottom += bar_size + padding + ( outline_size * 2.0f ); break;
}
if ( cfg.outline )
{
- draw_list.add_rect_filled( x - 1, y - 1, bar_w + 2, bar_h + 2, cfg.outline_color );
+ draw_list.add_rect_filled( x - outline_size, y - outline_size, bar_w + outline_size * 2.0f, bar_h + outline_size * 2.0f, cfg.outline_color );
}
draw_list.add_rect_filled( x, y, bar_w, bar_h, cfg.background_color );
@@ -456,37 +579,33 @@ namespace features::esp {
if ( cfg.gradient )
{
if ( vertical )
- {
draw_list.add_rect_filled_multi_color( x, y + bar_h - filled, bar_w, filled, cfg.full_color, cfg.full_color, cfg.low_color, cfg.low_color );
- }
else
- {
draw_list.add_rect_filled_multi_color( x, y, filled, bar_h, cfg.low_color, cfg.full_color, cfg.full_color, cfg.low_color );
- }
}
else
{
if ( vertical )
- {
draw_list.add_rect_filled( x, y + bar_h - filled, bar_w, filled, cfg.full_color );
- }
else
- {
draw_list.add_rect_filled( x, y, filled, bar_h, cfg.full_color );
- }
}
}
if ( cfg.show_value && clamped_health < 100 )
{
+ const auto text = std::to_string( clamped_health );
zdraw::push_font( g::render.fonts( ).pixel7_10 );
+ const auto [ tw, th ] = zdraw::measure_text( text );
- const auto text = std::to_string( clamped_health );
- const auto [text_w, text_h] = zdraw::measure_text( text );
- const auto text_x = std::floorf( x + ( bar_w * 0.5f ) - ( text_w * 0.5f ) );
- const auto text_y = vertical ? std::floorf( y + bar_h - filled - text_h - 2.0f ) : std::floorf( y - text_h - 2.0f );
+ const auto text_x = vertical
+ ? x + ( bar_w - tw ) * 0.5f
+ : x + filled - tw * 0.5f;
+ const auto text_y = vertical
+ ? y + bar_h - filled - th * 0.5f
+ : y + ( bar_h - th ) * 0.5f;
- draw_list.add_text( text_x, text_y, text, nullptr, cfg.text_color, zdraw::text_style::outlined );
+ draw_list.add_text( text_x, text_y, text, zdraw::get_font( ), cfg.text_color, zdraw::text_style::outlined );
zdraw::pop_font( );
}
}
@@ -514,7 +633,7 @@ namespace features::esp {
const auto fraction = anim.ammo.value( );
const auto outline_size = cfg.outline ? 1.0f : 0.0f;
- const auto vertical = cfg.position == settings::esp::player::ammo_bar::position_type::left;
+ const auto vertical = cfg.position == (int)settings::esp::player::ammo_bar::position::left;
const auto bar_w = vertical ? bar_size : std::floorf( bounds.width( ) );
const auto bar_h = vertical ? std::floorf( bounds.height( ) ) : bar_size;
@@ -522,7 +641,7 @@ namespace features::esp {
const auto x = [ & ]( )
{
- if ( cfg.position == settings::esp::player::ammo_bar::position_type::left )
+ if ( cfg.position == (int)settings::esp::player::ammo_bar::position::left )
{
return std::floorf( bounds.min.x - bar_size - padding - offsets.left - outline_size );
}
@@ -534,18 +653,18 @@ namespace features::esp {
{
switch ( cfg.position )
{
- case settings::esp::player::ammo_bar::position_type::left: return std::floorf( bounds.min.y );
- case settings::esp::player::ammo_bar::position_type::top: return std::floorf( bounds.min.y - bar_size - padding - offsets.top - outline_size );
- case settings::esp::player::ammo_bar::position_type::bottom: return std::floorf( bounds.max.y + padding + offsets.bottom + outline_size );
+ case (int)settings::esp::player::ammo_bar::position::left: return std::floorf( bounds.min.y );
+ case (int)settings::esp::player::ammo_bar::position::top: return std::floorf( bounds.min.y - bar_size - padding - offsets.top - outline_size );
+ case (int)settings::esp::player::ammo_bar::position::bottom: return std::floorf( bounds.max.y + padding + offsets.bottom + outline_size );
}
return 0.0f;
}( );
switch ( cfg.position )
{
- case settings::esp::player::ammo_bar::position_type::left: offsets.left += bar_size + padding + ( outline_size * 2.0f ); break;
- case settings::esp::player::ammo_bar::position_type::top: offsets.top += bar_size + padding + ( outline_size * 2.0f ); break;
- case settings::esp::player::ammo_bar::position_type::bottom: offsets.bottom += bar_size + padding + ( outline_size * 2.0f ); break;
+ case (int)settings::esp::player::ammo_bar::position::left: offsets.left += bar_size + padding + ( outline_size * 2.0f ); break;
+ case (int)settings::esp::player::ammo_bar::position::top: offsets.top += bar_size + padding + ( outline_size * 2.0f ); break;
+ case (int)settings::esp::player::ammo_bar::position::bottom: offsets.bottom += bar_size + padding + ( outline_size * 2.0f ); break;
}
if ( cfg.outline )
@@ -590,7 +709,7 @@ namespace features::esp {
const auto text_x = std::floorf( x + ( bar_w * 0.5f ) - ( text_w * 0.5f ) );
const auto text_y = vertical ? std::floorf( y + bar_h - filled - text_h - 2.0f ) : std::floorf( y + bar_h + 2.0f );
- draw_list.add_text( text_x, text_y, text, nullptr, cfg.text_color, zdraw::text_style::outlined );
+ draw_list.add_text( text_x, text_y, text, zdraw::get_font( ), cfg.text_color, zdraw::text_style::outlined );
zdraw::pop_font( );
}
}
@@ -603,7 +722,7 @@ namespace features::esp {
const auto text_x = std::floorf( bounds.min.x + ( bounds.width( ) * 0.5f ) - ( text_w * 0.5f ) );
const auto text_y = std::floorf( bounds.min.y - text_h - 2.0f - offsets.top );
- draw_list.add_text( text_x, text_y, player.display_name, nullptr, cfg.color, zdraw::text_style::outlined );
+ draw_list.add_text( text_x, text_y, player.display_name, zdraw::get_font( ), cfg.color, zdraw::text_style::outlined );
zdraw::pop_font( );
offsets.top += text_h + 2.0f;
@@ -615,7 +734,7 @@ namespace features::esp {
auto total_height{ 0.0f };
- if ( cfg.display == settings::esp::player::weapon::display_type::icon || cfg.display == settings::esp::player::weapon::display_type::text_and_icon )
+ if ( cfg.display == (int)settings::esp::player::weapon::display_type::icon || cfg.display == (int)settings::esp::player::weapon::display_type::text_and_icon )
{
zdraw::push_font( g::render.fonts( ).weapons_15 );
@@ -624,19 +743,19 @@ namespace features::esp {
const auto icon_x = std::floorf( bounds.min.x + ( bounds.width( ) * 0.5f ) - ( icon_w * 0.5f ) );
const auto icon_y = std::floorf( bounds.max.y + 2.0f + offsets.bottom + total_height );
- draw_list.add_text( icon_x, icon_y, icon, nullptr, cfg.icon_color, zdraw::text_style::outlined );
+ draw_list.add_text( icon_x, icon_y, icon, zdraw::get_font( ), cfg.icon_color, zdraw::text_style::outlined );
zdraw::pop_font( );
total_height += icon_h + 2.0f;
}
- if ( cfg.display == settings::esp::player::weapon::display_type::text || cfg.display == settings::esp::player::weapon::display_type::text_and_icon )
+ if ( cfg.display == (int)settings::esp::player::weapon::display_type::text || cfg.display == (int)settings::esp::player::weapon::display_type::text_and_icon )
{
const auto [text_w, text_h] = zdraw::measure_text( player.weapon.name );
const auto text_x = std::floorf( bounds.min.x + ( bounds.width( ) * 0.5f ) - ( text_w * 0.5f ) );
const auto text_y = std::floorf( bounds.max.y + 2.0f + offsets.bottom + total_height );
- draw_list.add_text( text_x, text_y, player.weapon.name, nullptr, cfg.text_color, zdraw::text_style::outlined );
+ draw_list.add_text( text_x, text_y, player.weapon.name, zdraw::get_font( ), cfg.text_color, zdraw::text_style::outlined );
total_height += text_h + 2.0f;
}
@@ -656,7 +775,7 @@ namespace features::esp {
const auto draw_flag = [ & ]( const std::string& text, const zdraw::rgba& color )
{
- draw_list.add_text( x, y, text, nullptr, color, zdraw::text_style::outlined );
+ draw_list.add_text( x, y, text, zdraw::get_font( ), color, zdraw::text_style::outlined );
const auto [text_w, text_h] = zdraw::measure_text( text );
y += text_h;
max_w = std::max( max_w, text_w );
@@ -741,4 +860,281 @@ namespace features::esp {
return it != icons.end( ) ? it->second : "?";
}
-} // namespace features::esp
\ No newline at end of file
+ void player::draw_movement_trails( zdraw::draw_list& draw_list )
+ {
+ const auto& cfg = settings::g_esp.m_player.m_trails;
+ if ( !cfg.enabled )
+ {
+ this->m_trails.clear( );
+ return;
+ }
+
+ const auto global_vars = g::memory.read( g::offsets.global_vars );
+ if ( !global_vars ) return;
+
+ const auto current_time = g::memory.read( global_vars + 0x30 );
+
+ const auto local_pawn = systems::g_local.pawn( );
+
+ // Update trails for active players
+ std::unordered_set active_pawns;
+
+ auto update_trail = [&]( std::uintptr_t pawn, const math::vector3& origin, bool should_track )
+ {
+ if ( !should_track )
+ {
+ this->m_trails.erase( pawn );
+ return;
+ }
+
+ active_pawns.insert( pawn );
+ auto& trail = this->m_trails[ pawn ];
+
+ // Remove expired points
+ while ( !trail.path.empty( ) && ( current_time - trail.path.front( ).time ) > cfg.lifetime )
+ {
+ trail.path.pop_front( );
+ }
+
+ if ( trail.path.empty( ) || ( trail.path.back( ).pos - origin ).length_sqr( ) > 1.0f )
+ {
+ trail.path.push_back( { origin, current_time } );
+ if ( static_cast< int >( trail.path.size( ) ) > cfg.max_points )
+ {
+ trail.path.pop_front( );
+ }
+ }
+ };
+
+ // Track local player
+ if ( local_pawn && cfg.local )
+ {
+ const auto gsn = g::memory.read( local_pawn + SCHEMA( "C_BaseEntity", "m_pGameSceneNode"_hash ) );
+ if ( gsn )
+ {
+ const auto origin = g::memory.read( gsn + SCHEMA( "CGameSceneNode", "m_vecAbsOrigin"_hash ) );
+ update_trail( local_pawn, origin, true );
+ }
+ }
+
+ // Track others
+ for ( const auto& player : systems::g_collector.players( ) )
+ {
+ if ( !player.pawn || player.pawn == local_pawn )
+ continue;
+
+ const bool is_enemy = systems::g_local.is_enemy( player.team );
+ const bool is_team = !is_enemy;
+
+ bool should_track = false;
+ if ( is_enemy && cfg.enemy ) should_track = true;
+ else if ( is_team && cfg.team ) should_track = true;
+
+ update_trail( player.pawn, player.origin, should_track );
+ }
+
+ // Cleanup trails for inactive players
+ for ( auto it = this->m_trails.begin( ); it != this->m_trails.end( ); )
+ {
+ if ( active_pawns.find( it->first ) == active_pawns.end( ) )
+ {
+ it = this->m_trails.erase( it );
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ // Draw trails
+ for ( const auto& [ pawn, data ] : this->m_trails )
+ {
+ if ( data.path.size( ) < 2 )
+ continue;
+
+ zdraw::rgba color;
+ if ( pawn == local_pawn ) color = cfg.local_color;
+ else
+ {
+ bool found_team = false;
+ for ( const auto& p : systems::g_collector.players( ) )
+ {
+ if ( p.pawn == pawn )
+ {
+ color = systems::g_local.is_enemy( p.team ) ? cfg.enemy_color : cfg.team_color;
+ found_team = true;
+ break;
+ }
+ }
+ if ( !found_team ) continue;
+ }
+
+ for ( size_t i = 1; i < data.path.size( ); ++i )
+ {
+ const auto& p1_data = data.path[ i - 1 ];
+ const auto& p2_data = data.path[ i ];
+
+ const auto p1_screen = systems::g_view.project( p1_data.pos );
+ const auto p2_screen = systems::g_view.project( p2_data.pos );
+
+ if ( systems::g_view.projection_valid( p1_screen ) && systems::g_view.projection_valid( p2_screen ) )
+ {
+ const float age1 = current_time - p1_data.time;
+ const float age2 = current_time - p2_data.time;
+
+ // Average age alpha or segment based alpha
+ const float fraction = 1.0f - ( age2 / cfg.lifetime );
+ const auto alpha = static_cast< std::uint8_t >( std::clamp( fraction, 0.0f, 1.0f ) * color.a );
+
+ if ( alpha > 0 )
+ {
+ draw_list.add_line( p1_screen.x, p1_screen.y, p2_screen.x, p2_screen.y, zdraw::rgba{ color.r, color.g, color.b, alpha }, cfg.thickness );
+ }
+ }
+ }
+ }
+ }
+
+ void player::add_capsule( zdraw::draw_list& draw_list, const math::vector3& start, const math::vector3& end, float radius, const math::quaternion& rotation, const math::vector3& origin, const zdraw::rgba& color, int segments_max, bool red_only, float fill_line )
+ {
+ const auto top = rotation.rotate_vector( start ) + origin;
+ const auto bottom = rotation.rotate_vector( end ) + origin;
+
+ const auto axis = ( bottom - top ).normalized( );
+ const auto arbitrary = std::abs( axis.x ) < 0.99f ? math::vector3( 1, 0, 0 ) : math::vector3( 0, 1, 0 );
+ const auto u = axis.cross( arbitrary ).normalized( );
+ const auto v = axis.cross( u );
+
+ const auto capsule_mid_point = ( top + bottom ) * 0.5f;
+ const auto distance = capsule_mid_point.distance( systems::g_view.origin( ) );
+
+ const auto start_reduction_distance = 800.0f;
+ const auto end_reduction_distance = 3000.0f;
+
+ int min_segments = 4;
+ int current_segments;
+
+ if ( distance <= start_reduction_distance )
+ {
+ current_segments = segments_max;
+ }
+ else if ( distance >= end_reduction_distance )
+ {
+ current_segments = min_segments;
+ }
+ else
+ {
+ auto normalized_distance = ( distance - start_reduction_distance ) / ( end_reduction_distance - start_reduction_distance );
+ normalized_distance = std::clamp( normalized_distance, 0.0f, 1.0f );
+ current_segments = static_cast< int >( std::lerp( static_cast< float >( segments_max ), static_cast< float >( min_segments ), normalized_distance ) );
+ current_segments = std::max( current_segments, min_segments );
+ }
+
+ this->precompute_sincos( current_segments );
+ this->draw_capsule_outline( draw_list, top, bottom, axis, u, v, radius, color, current_segments, red_only, fill_line );
+ }
+
+ void player::draw_capsule_outline( zdraw::draw_list& draw_list, const math::vector3& top, const math::vector3& bottom, const math::vector3& axis, const math::vector3& u, const math::vector3& v, float radius, const zdraw::rgba& color, int segments, bool red_only, float fill_line )
+ {
+ std::vector top_circle, bottom_circle;
+ this->create_circle( top, u, v, radius, top_circle, segments );
+ this->create_circle( bottom, u, v, radius, bottom_circle, segments );
+
+ const auto hemisphere_segments = std::max( 3, segments / 3 );
+
+ std::vector wtop( segments + 1 ), wbottom( segments + 1 );
+ for ( int i = 0; i <= segments; ++i )
+ {
+ wtop[ i ] = systems::g_view.project( top_circle[ i ] );
+ wbottom[ i ] = systems::g_view.project( bottom_circle[ i ] );
+ }
+
+ const auto thickness = 1.25f;
+ const auto draw_clipped_line = [ & ]( const math::vector2& p1, const math::vector2& p2, const zdraw::rgba& col, float thick )
+ {
+ if ( !systems::g_view.projection_valid( p1 ) || !systems::g_view.projection_valid( p2 ) ) return;
+
+ if ( red_only )
+ {
+ if ( p1.y < fill_line && p2.y < fill_line ) return;
+ if ( p1.y >= fill_line && p2.y >= fill_line )
+ {
+ draw_list.add_line( p1.x, p1.y, p2.x, p2.y, col, thick );
+ }
+ else
+ {
+ const float t = ( fill_line - p1.y ) / ( p2.y - p1.y );
+ const math::vector2 intersection{ p1.x + t * ( p2.x - p1.x ), fill_line };
+ if ( p1.y >= fill_line ) draw_list.add_line( p1.x, p1.y, intersection.x, intersection.y, col, thick );
+ else draw_list.add_line( intersection.x, intersection.y, p2.x, p2.y, col, thick );
+ }
+ }
+ else
+ {
+ draw_list.add_line( p1.x, p1.y, p2.x, p2.y, col, thick );
+ }
+ };
+
+ for ( int i = 0; i < segments; ++i )
+ {
+ draw_clipped_line( wtop[ i ], wtop[ i + 1 ], color, thickness );
+ draw_clipped_line( wbottom[ i ], wbottom[ i + 1 ], color, thickness );
+ }
+
+ for ( int h = 0; h < hemisphere_segments; ++h )
+ {
+ const auto phi = ( std::numbers::pi_v / 2.0f ) * ( static_cast< float >( h + 1 ) / hemisphere_segments );
+ const auto ring_radius = radius * std::cos( phi );
+ const auto ring_height = radius * std::sin( phi );
+
+ std::vector top_arc, bottom_arc;
+ const auto top_ring_center = top - axis * ring_height;
+ const auto bottom_ring_center = bottom + axis * ring_height;
+
+ this->create_circle( top_ring_center, u, v, ring_radius, top_arc, segments );
+ this->create_circle( bottom_ring_center, u, v, ring_radius, bottom_arc, segments );
+
+ for ( int i = 0; i < segments; ++i )
+ {
+ draw_clipped_line( systems::g_view.project( top_arc[ i ] ), systems::g_view.project( top_arc[ i + 1 ] ), color, thickness * 0.7f );
+ draw_clipped_line( systems::g_view.project( bottom_arc[ i ] ), systems::g_view.project( bottom_arc[ i + 1 ] ), color, thickness * 0.7f );
+ }
+ }
+
+ const auto half = segments / 2;
+ const auto quarter = segments / 4;
+ const auto three_quarter = ( 3 * segments ) / 4;
+
+ draw_clipped_line( wtop[ 0 ], wbottom[ 0 ], color, thickness );
+ draw_clipped_line( wtop[ half ], wbottom[ half ], color, thickness );
+ draw_clipped_line( wtop[ quarter ], wbottom[ quarter ], color, thickness * 0.8f );
+ draw_clipped_line( wtop[ three_quarter ], wbottom[ three_quarter ], color, thickness * 0.8f );
+ }
+
+ void player::precompute_sincos( int segments )
+ {
+ this->m_sin_cache.resize( segments + 1 );
+ this->m_cos_cache.resize( segments + 1 );
+
+ const auto angle_step = 2.0f * std::numbers::pi_v / segments;
+ for ( int i = 0; i <= segments; ++i )
+ {
+ const auto angle = angle_step * i;
+ this->m_sin_cache[ i ] = std::sin( angle );
+ this->m_cos_cache[ i ] = std::cos( angle );
+ }
+ }
+
+ void player::create_circle( const math::vector3& center, const math::vector3& u, const math::vector3& v, float radius, std::vector& out, int segments )
+ {
+ out.clear( );
+ out.reserve( segments + 1 );
+
+ for ( int i = 0; i <= segments; ++i )
+ {
+ out.push_back( center + ( u * this->m_cos_cache[ i ] + v * this->m_sin_cache[ i ] ) * radius );
+ }
+ }
+
+
+} // namespace features::esp
diff --git a/catalyst/project/core/features/impl/esp/projectile.cpp b/catalyst/project/core/features/impl/esp/projectile.cpp
index 1d5a9f5..edcf230 100644
--- a/catalyst/project/core/features/impl/esp/projectile.cpp
+++ b/catalyst/project/core/features/impl/esp/projectile.cpp
@@ -2,304 +2,339 @@
namespace features::esp {
- void projectile::on_render( zdraw::draw_list& draw_list )
- {
- const auto& cfg = settings::g_esp.m_projectile;
- if ( !cfg.enabled )
- {
- return;
- }
-
- const auto current_time = g::memory.read( g::memory.read( g::offsets.global_vars ) + 0x30 );
-
- for ( const auto& proj : systems::g_collector.projectiles( ) )
- {
- if ( proj.origin.length_sqr( ) < 1.0f )
- {
- continue;
- }
-
- if ( proj.detonated )
- {
- continue;
- }
-
- if ( proj.subtype == systems::collector::projectile_subtype::molotov_fire )
- {
- if ( cfg.show_inferno_bounds && !proj.fire_points.empty( ) )
- {
- this->draw_inferno_bounds( draw_list, proj, cfg );
- }
-
- const auto screen = systems::g_view.project( proj.origin );
- if ( !systems::g_view.projection_valid( screen ) )
- {
- continue;
- }
-
- auto y_offset{ 0.0f };
-
- if ( cfg.show_icon )
- {
- zdraw::push_font( g::render.fonts( ).weapons_15 );
-
- const auto icon = std::string( "l" );
- const auto [tw, th] = zdraw::measure_text( icon );
- const auto x = std::floorf( screen.x - tw * 0.5f );
- const auto y = std::floorf( screen.y - th * 0.5f );
-
- draw_list.add_text( x, y, icon, nullptr, cfg.color_molotov, zdraw::text_style::outlined );
- zdraw::pop_font( );
-
- y_offset += th - 5.5f;
- }
-
- if ( cfg.show_name )
- {
- zdraw::push_font( g::render.fonts( ).pixel7_10 );
-
- const auto name = std::string( "fire" );
- const auto [tw, th] = zdraw::measure_text( name );
- const auto x = std::floorf( screen.x - tw * 0.5f );
- const auto y = std::floorf( screen.y + y_offset );
-
- draw_list.add_text( x, y, name, nullptr, cfg.color_molotov, zdraw::text_style::outlined );
- zdraw::pop_font( );
-
- y_offset += th - 5.5f;
- }
-
- if ( proj.expire_time > 0.0f )
- {
- constexpr auto inferno_duration{ 7.0f };
- const auto remaining = std::max( 0.0f, proj.expire_time - current_time );
- const auto frac = std::clamp( remaining / inferno_duration, 0.0f, 1.0f );
-
- this->draw_timer( draw_list, screen, y_offset, remaining, frac, cfg );
- }
-
- continue;
- }
-
- const auto screen = systems::g_view.project( proj.origin );
- if ( !systems::g_view.projection_valid( screen ) )
- {
- continue;
- }
-
- const auto color = get_color( proj.subtype, cfg );
- auto y_offset{ 0.0f };
-
- if ( cfg.show_icon )
- {
- zdraw::push_font( g::render.fonts( ).weapons_15 );
-
- const auto icon = this->get_icon( proj.subtype );
- const auto [tw, th] = zdraw::measure_text( icon );
- const auto x = std::floorf( screen.x - tw * 0.5f );
- const auto y = std::floorf( screen.y - th * 0.5f );
-
- draw_list.add_text( x, y, icon, nullptr, color, zdraw::text_style::outlined );
- zdraw::pop_font( );
-
- y_offset += th - 5.5f;
- }
-
- if ( cfg.show_name )
- {
- zdraw::push_font( g::render.fonts( ).pixel7_10 );
-
- const auto name = this->get_name( proj.subtype );
- const auto [tw, th] = zdraw::measure_text( name );
- const auto x = std::floorf( screen.x - tw * 0.5f );
- const auto y = std::floorf( screen.y + y_offset );
-
- draw_list.add_text( x, y, name, nullptr, color, zdraw::text_style::outlined );
- zdraw::pop_font( );
-
- y_offset += th - 5.5f;
- }
-
- if ( proj.subtype == systems::collector::projectile_subtype::smoke_grenade && proj.smoke_active )
- {
- constexpr auto smoke_duration{ 18.0f };
- const auto smoke_start = static_cast< float >( proj.effect_tick_begin ) * ( 1.0f / 64.0f );
- const auto remaining = std::max( 0.0f, smoke_duration - ( current_time - smoke_start ) );
- const auto frac = std::clamp( remaining / smoke_duration, 0.0f, 1.0f );
-
- this->draw_timer( draw_list, screen, y_offset, remaining, frac, cfg );
- }
-
- if ( proj.subtype == systems::collector::projectile_subtype::decoy && proj.effect_tick_begin > 0 )
- {
- const auto fuse{ 15.0f };
- const auto throw_time = static_cast< float >( proj.effect_tick_begin ) * ( 1.0f / 64.0f );
- const auto remaining = std::max( 0.0f, fuse - ( current_time - throw_time ) );
- const auto frac = std::clamp( remaining / fuse, 0.0f, 1.0f );
-
- this->draw_timer( draw_list, screen, y_offset, remaining, frac, cfg );
- }
- }
- }
-
- void projectile::draw_timer( zdraw::draw_list& draw_list, const math::vector2& screen, float& y_offset, float remaining, float frac, const settings::esp::projectile& cfg ) const
- {
- if ( cfg.show_timer_bar )
- {
- const auto timer_color = this->lerp_color( cfg.timer_low_color, cfg.timer_high_color, frac );
- const auto bar_w{ 30.0f };
- const auto bar_h{ 3.0f };
- const auto bx = std::floorf( screen.x - bar_w * 0.5f );
- const auto by = std::floorf( screen.y + y_offset + 6.0f );
-
- draw_list.add_rect_filled( bx - 1.0f, by - 1.0f, bar_w + 2.0f, bar_h + 2.0f, cfg.bar_background );
- draw_list.add_rect_filled( bx, by, bar_w * frac, bar_h, timer_color );
-
- y_offset += bar_h + 2.0f;
- }
- }
-
- void projectile::draw_inferno_bounds( zdraw::draw_list& draw_list, const systems::collector::projectile& proj, const settings::esp::projectile& cfg ) const
- {
- constexpr auto fire_radius{ 60.0f };
- constexpr auto points_per_fire{ 12 };
-
- std::vector points{};
- points.reserve( proj.fire_points.size( ) * points_per_fire );
-
- for ( const auto& point : proj.fire_points )
- {
- for ( int i = 0; i < points_per_fire; ++i )
- {
- const auto angle = static_cast< float >( i ) / points_per_fire * std::numbers::pi_v *2.0f;
- const auto world = point + math::vector3{ std::cosf( angle ) * fire_radius, std::sinf( angle ) * fire_radius, 0.0f };
- const auto projected = systems::g_view.project( world );
-
- if ( systems::g_view.projection_valid( projected ) )
- {
- points.push_back( projected );
- }
- }
- }
-
- if ( points.size( ) < 3 )
- {
- return;
- }
-
- std::ranges::sort( points, [ ]( const math::vector2& a, const math::vector2& b ) { return a.x < b.x || ( a.x == b.x && a.y < b.y ); } );
-
- std::vector lower{};
- std::vector upper{};
-
- for ( const auto& p : points )
- {
- while ( lower.size( ) >= 2 )
- {
- const auto& p1 = lower[ lower.size( ) - 2 ];
- const auto& p2 = lower[ lower.size( ) - 1 ];
-
- if ( ( p2.x - p1.x ) * ( p.y - p1.y ) - ( p2.y - p1.y ) * ( p.x - p1.x ) > 0.0f )
- {
- break;
- }
-
- lower.pop_back( );
- }
- lower.push_back( p );
- }
-
- for ( auto it = points.rbegin( ); it != points.rend( ); ++it )
- {
- while ( upper.size( ) >= 2 )
- {
- const auto& p1 = upper[ upper.size( ) - 2 ];
- const auto& p2 = upper[ upper.size( ) - 1 ];
-
- if ( ( p2.x - p1.x ) * ( it->y - p1.y ) - ( p2.y - p1.y ) * ( it->x - p1.x ) > 0.0f )
- {
- break;
- }
-
- upper.pop_back( );
- }
- upper.push_back( *it );
- }
-
- lower.pop_back( );
- upper.pop_back( );
- lower.insert( lower.end( ), upper.begin( ), upper.end( ) );
-
- if ( lower.size( ) < 3 )
- {
- return;
- }
-
- const auto hull = std::span( reinterpret_cast< const float* >( lower.data( ) ), lower.size( ) * 2 );
- const auto fill = zdraw::rgba( cfg.color_molotov.r, cfg.color_molotov.g, cfg.color_molotov.b, 50 );
- const auto outline = zdraw::rgba( cfg.color_molotov.r, cfg.color_molotov.g, cfg.color_molotov.b, 150 );
-
- draw_list.add_convex_poly_filled( hull, fill );
- draw_list.add_polyline( hull, outline, true, 2.0f );
- }
-
- zdraw::rgba projectile::get_color( systems::collector::projectile_subtype type, const settings::esp::projectile& cfg ) const
- {
- using st = systems::collector::projectile_subtype;
-
- switch ( type )
- {
- case st::he_grenade: return cfg.color_he;
- case st::flashbang: return cfg.color_flash;
- case st::smoke_grenade: return cfg.color_smoke;
- case st::molotov: return cfg.color_molotov;
- case st::molotov_fire: return cfg.color_molotov;
- case st::decoy: return cfg.color_decoy;
- default: return cfg.default_color;
- }
- }
-
- std::string projectile::get_icon( systems::collector::projectile_subtype type ) const
- {
- using st = systems::collector::projectile_subtype;
-
- switch ( type )
- {
- case st::he_grenade: return "j";
- case st::flashbang: return "i";
- case st::smoke_grenade: return "k";
- case st::molotov: return "l";
- case st::molotov_fire: return "l";
- case st::decoy: return "m";
- default: return "?";
- }
- }
-
- std::string projectile::get_name( systems::collector::projectile_subtype type ) const
- {
- using st = systems::collector::projectile_subtype;
-
- switch ( type )
- {
- case st::he_grenade: return "he";
- case st::flashbang: return "flash";
- case st::smoke_grenade: return "smoke";
- case st::molotov: return "molotov";
- case st::molotov_fire: return "fire";
- case st::decoy: return "decoy";
- default: return "grenade";
- }
- }
-
- zdraw::rgba projectile::lerp_color( const zdraw::rgba& a, const zdraw::rgba& b, float t ) const
- {
- return zdraw::rgba
- (
- static_cast< std::uint8_t >( a.r + ( b.r - a.r ) * t ),
- static_cast< std::uint8_t >( a.g + ( b.g - a.g ) * t ),
- static_cast< std::uint8_t >( a.b + ( b.b - a.b ) * t ),
- static_cast< std::uint8_t >( a.a + ( b.a - a.a ) * t )
- );
- }
+ void projectile::on_render( zdraw::draw_list& draw_list )
+ {
+ const auto& cfg = settings::g_esp.m_projectile;
+ if ( !cfg.enabled )
+ {
+ return;
+ }
+
+ const auto global_vars = g::memory.read( g::offsets.global_vars );
+ if ( !global_vars )
+ return;
+
+ const auto current_time = g::memory.read( global_vars + 0x30 );
+
+ for ( const auto& proj : systems::g_collector.projectiles( ) )
+ {
+ if ( proj.origin.length_sqr( ) < 0.1f )
+ {
+ continue;
+ }
+
+ if ( proj.detonated )
+ {
+ continue;
+ }
+
+ if ( proj.subtype == systems::collector::projectile_subtype::molotov_fire )
+ {
+ if ( cfg.show_inferno_bounds && !proj.fire_points.empty( ) )
+ {
+ this->draw_inferno_bounds( draw_list, proj, cfg );
+ }
+
+ const auto screen = systems::g_view.project( proj.origin );
+ if ( !systems::g_view.projection_valid( screen ) )
+ {
+ continue;
+ }
+
+ auto y_offset{ 0.0f };
+
+ if ( cfg.show_icon )
+ {
+ zdraw::push_font( g::render.fonts( ).weapons_40 );
+
+ const auto icon = std::string( "l" );
+ const auto [tw, th] = zdraw::measure_text( icon );
+ const auto x = std::floorf( screen.x - tw * 0.5f );
+ const auto y = std::floorf( screen.y - th * 0.5f );
+
+ draw_list.add_text( x, y, icon, zdraw::get_font( ), cfg.color_molotov, zdraw::text_style::outlined );
+ zdraw::pop_font( );
+
+ y_offset += th - 5.5f;
+ }
+
+ if ( cfg.show_name )
+ {
+ zdraw::push_font( g::render.fonts( ).pixel7_10 );
+
+ const auto name = std::string( "fire" );
+ const auto [tw, th] = zdraw::measure_text( name );
+ const auto x = std::floorf( screen.x - tw * 0.5f );
+ const auto y = std::floorf( screen.y + y_offset );
+
+ draw_list.add_text( x, y, name, zdraw::get_font( ), cfg.color_molotov, zdraw::text_style::outlined );
+ zdraw::pop_font( );
+
+ y_offset += th - 5.5f;
+ }
+
+ if ( proj.expire_time > 0.0f )
+ {
+ constexpr auto inferno_duration{ 7.0f };
+ const auto remaining = std::max( 0.0f, proj.expire_time - current_time );
+ const auto frac = std::clamp( remaining / inferno_duration, 0.0f, 1.0f );
+
+ this->draw_timer( draw_list, screen, y_offset, remaining, frac, cfg );
+ }
+
+ continue;
+ }
+
+ const auto screen = systems::g_view.project( proj.origin );
+ if ( !systems::g_view.projection_valid( screen ) )
+ {
+ continue;
+ }
+
+ const auto color = get_color( proj.subtype, cfg );
+ auto y_offset{ 0.0f };
+
+ if ( cfg.show_icon )
+ {
+ zdraw::push_font( g::render.fonts( ).weapons_40 );
+
+ const auto icon = this->get_icon( proj.subtype );
+ const auto [tw, th] = zdraw::measure_text( icon );
+ const auto x = std::floorf( screen.x - tw * 0.5f );
+ const auto y = std::floorf( screen.y - th * 0.5f );
+
+ draw_list.add_text( x, y, icon, zdraw::get_font( ), color, zdraw::text_style::outlined );
+ zdraw::pop_font( );
+
+ y_offset += th - 5.5f;
+ }
+
+ if ( cfg.show_name )
+ {
+ zdraw::push_font( g::render.fonts( ).pixel7_10 );
+
+ const auto name = this->get_name( proj.subtype );
+ const auto [tw, th] = zdraw::measure_text( name );
+ const auto x = std::floorf( screen.x - tw * 0.5f );
+ const auto y = std::floorf( screen.y + y_offset );
+
+ draw_list.add_text( x, y, name, zdraw::get_font( ), color, zdraw::text_style::outlined );
+ zdraw::pop_font( );
+
+ y_offset += th - 5.5f;
+ }
+
+ if ( proj.subtype == systems::collector::projectile_subtype::smoke_grenade && proj.smoke_active )
+ {
+ constexpr auto smoke_duration{ 18.0f };
+ const auto smoke_start = static_cast< float >( proj.effect_tick_begin ) * ( 1.0f / 64.0f );
+ const auto remaining = std::max( 0.0f, smoke_duration - ( current_time - smoke_start ) );
+ const auto frac = std::clamp( remaining / smoke_duration, 0.0f, 1.0f );
+
+ this->draw_timer( draw_list, screen, y_offset, remaining, frac, cfg );
+ }
+
+ if ( proj.subtype == systems::collector::projectile_subtype::decoy && proj.effect_tick_begin > 0 )
+ {
+ const auto fuse{ 15.0f };
+ const auto throw_time = static_cast< float >( proj.effect_tick_begin ) * ( 1.0f / 64.0f );
+ const auto remaining = std::max( 0.0f, fuse - ( current_time - throw_time ) );
+ const auto frac = std::clamp( remaining / fuse, 0.0f, 1.0f );
+
+ this->draw_timer( draw_list, screen, y_offset, remaining, frac, cfg );
+ }
+ }
+
+ this->draw_smoke_voxels( draw_list, cfg );
+ }
+
+ void projectile::draw_timer( zdraw::draw_list& draw_list, const math::vector2& screen, float& y_offset, float remaining, float frac, const settings::esp::projectile& cfg ) const
+ {
+ if ( cfg.show_timer_bar )
+ {
+ const auto timer_color = this->lerp_color( cfg.timer_low_color, cfg.timer_high_color, frac );
+ const auto bar_w{ 30.0f };
+ const auto bar_h{ 3.0f };
+ const auto bx = std::floorf( screen.x - bar_w * 0.5f );
+ const auto by = std::floorf( screen.y + y_offset + 6.0f );
+
+ draw_list.add_rect_filled( bx - 1.0f, by - 1.0f, bar_w + 2.0f, bar_h + 2.0f, cfg.bar_background );
+ draw_list.add_rect_filled( bx, by, bar_w * frac, bar_h, timer_color );
+
+ y_offset += bar_h + 2.0f;
+ }
+ }
+
+ void projectile::draw_inferno_bounds( zdraw::draw_list& draw_list, const systems::collector::projectile& proj, const settings::esp::projectile& cfg ) const
+ {
+ constexpr auto fire_radius{ 60.0f };
+ constexpr auto points_per_fire{ 12 };
+
+ std::vector points{};
+ points.reserve( proj.fire_points.size( ) * points_per_fire );
+
+ for ( const auto& point : proj.fire_points )
+ {
+ for ( int i = 0; i < points_per_fire; ++i )
+ {
+ const auto angle = static_cast< float >( i ) / points_per_fire * std::numbers::pi_v *2.0f;
+ const auto world = point + math::vector3{ std::cosf( angle ) * fire_radius, std::sinf( angle ) * fire_radius, 0.0f };
+ const auto projected = systems::g_view.project( world );
+
+ if ( systems::g_view.projection_valid( projected ) )
+ {
+ points.push_back( projected );
+ }
+ }
+ }
+
+ if ( points.size( ) < 3 )
+ {
+ return;
+ }
+
+ std::ranges::sort( points, [ ]( const math::vector2& a, const math::vector2& b ) { return a.x < b.x || ( a.x == b.x && a.y < b.y ); } );
+
+ std::vector lower{};
+ std::vector upper{};
+
+ for ( const auto& p : points )
+ {
+ while ( lower.size( ) >= 2 )
+ {
+ const auto& p1 = lower[ lower.size( ) - 2 ];
+ const auto& p2 = lower[ lower.size( ) - 1 ];
+
+ if ( ( p2.x - p1.x ) * ( p.y - p1.y ) - ( p2.y - p1.y ) * ( p.x - p1.x ) > 0.0f )
+ {
+ break;
+ }
+
+ lower.pop_back( );
+ }
+ lower.push_back( p );
+ }
+
+ for ( auto it = points.rbegin( ); it != points.rend( ); ++it )
+ {
+ while ( upper.size( ) >= 2 )
+ {
+ const auto& p1 = upper[ upper.size( ) - 2 ];
+ const auto& p2 = upper[ upper.size( ) - 1 ];
+
+ if ( ( p2.x - p1.x ) * ( it->y - p1.y ) - ( p2.y - p1.y ) * ( it->x - p1.x ) > 0.0f )
+ {
+ break;
+ }
+
+ upper.pop_back( );
+ }
+ upper.push_back( *it );
+ }
+
+ lower.pop_back( );
+ upper.pop_back( );
+ lower.insert( lower.end( ), upper.begin( ), upper.end( ) );
+
+ if ( lower.size( ) < 3 )
+ {
+ return;
+ }
+
+ const auto hull = std::span( reinterpret_cast< const float* >( lower.data( ) ), lower.size( ) * 2 );
+ const auto fill = zdraw::rgba( cfg.color_molotov.r, cfg.color_molotov.g, cfg.color_molotov.b, 50 );
+ const auto outline = zdraw::rgba( cfg.color_molotov.r, cfg.color_molotov.g, cfg.color_molotov.b, 150 );
+
+ draw_list.add_convex_poly_filled( hull, fill );
+ draw_list.add_polyline( hull, outline, true, 2.0f );
+ }
+
+ void projectile::draw_smoke_voxels( zdraw::draw_list& draw_list, const settings::esp::projectile& cfg ) const
+ {
+ if ( !cfg.show_smoke_voxels )
+ {
+ return;
+ }
+
+ const auto& voxels = systems::g_voxels.active_voxels( );
+ if ( voxels.empty( ) )
+ {
+ return;
+ }
+
+ for ( const auto& v : voxels )
+ {
+ const auto screen = systems::g_view.project( v.world );
+ if ( !systems::g_view.projection_valid( screen ) )
+ {
+ continue;
+ }
+
+ const auto color = zdraw::rgba{ v.r, v.g, v.b, static_cast< std::uint8_t >( v.a * 0.4f ) };
+ draw_list.add_rect_filled( screen.x - 2.0f, screen.y - 2.0f, 4.0f, 4.0f, color );
+ }
+ }
+
+ zdraw::rgba projectile::get_color( systems::collector::projectile_subtype type, const settings::esp::projectile& cfg ) const
+ {
+ using st = systems::collector::projectile_subtype;
+
+ switch ( type )
+ {
+ case st::he_grenade: return cfg.color_he;
+ case st::flashbang: return cfg.color_flash;
+ case st::smoke_grenade: return cfg.color_smoke;
+ case st::molotov: return cfg.color_molotov;
+ case st::incendiary: return cfg.color_molotov;
+ case st::molotov_fire: return cfg.color_molotov;
+ case st::decoy: return cfg.color_decoy;
+ default: return cfg.default_color;
+ }
+ }
+
+ std::string projectile::get_icon( systems::collector::projectile_subtype type ) const
+ {
+ using st = systems::collector::projectile_subtype;
+
+ switch ( type )
+ {
+ case st::he_grenade: return "j";
+ case st::flashbang: return "i";
+ case st::smoke_grenade: return "k";
+ case st::molotov: return "l";
+ case st::incendiary: return "n";
+ case st::molotov_fire: return "l";
+ case st::decoy: return "m";
+ default: return "?";
+ }
+ }
+
+ std::string projectile::get_name( systems::collector::projectile_subtype type ) const
+ {
+ using st = systems::collector::projectile_subtype;
+
+ switch ( type )
+ {
+ case st::he_grenade: return "he";
+ case st::flashbang: return "flash";
+ case st::smoke_grenade: return "smoke";
+ case st::molotov: return "molotov";
+ case st::incendiary: return "incendiary";
+ case st::molotov_fire: return "fire";
+ case st::decoy: return "decoy";
+ default: return "grenade";
+ }
+ }
+
+ zdraw::rgba projectile::lerp_color( const zdraw::rgba& a, const zdraw::rgba& b, float t ) const
+ {
+ return zdraw::rgba
+ (
+ static_cast< std::uint8_t >( a.r + ( b.r - a.r ) * t ),
+ static_cast< std::uint8_t >( a.g + ( b.g - a.g ) * t ),
+ static_cast< std::uint8_t >( a.b + ( b.b - a.b ) * t ),
+ static_cast< std::uint8_t >( a.a + ( b.a - a.a ) * t )
+ );
+ }
} // namespace features::esp
\ No newline at end of file
diff --git a/catalyst/project/core/features/impl/misc/grenades.cpp b/catalyst/project/core/features/impl/misc/grenades.cpp
index 0ad0949..6bebe78 100644
--- a/catalyst/project/core/features/impl/misc/grenades.cpp
+++ b/catalyst/project/core/features/impl/misc/grenades.cpp
@@ -26,7 +26,7 @@ namespace features::misc {
}
const auto fade_elapsed = std::chrono::duration( now - g.detonate_time ).count( );
- if ( fade_elapsed <= 0.5f )
+ if ( fade_elapsed <= cfg.fade_duration )
{
return false;
}
@@ -68,12 +68,12 @@ namespace features::misc {
if ( gren.detonated )
{
const auto elapsed = std::chrono::duration( now - gren.detonate_time ).count( );
- alpha = std::clamp( 1.0f - elapsed / 0.5f, 0.0f, 1.0f );
+ alpha = std::clamp( 1.0f - elapsed / cfg.fade_duration, 0.0f, 1.0f );
}
if ( alpha > 0.0f )
{
- this->render_trajectory( draw_list, gren.traj, alpha );
+ this->render_trajectory( draw_list, gren.traj, alpha, gren.weapon_hash );
}
}
@@ -108,7 +108,7 @@ namespace features::misc {
if ( traj.valid )
{
- this->render_trajectory( draw_list, traj, 1.0f );
+ this->render_trajectory( draw_list, traj, 1.0f, this->m_weapon_hash );
}
}
@@ -363,6 +363,27 @@ namespace features::misc {
}
}
+ zdraw::rgba grenades::color_for_type( std::uintptr_t weapon_hash ) const
+ {
+ const auto& cfg = settings::g_misc.m_grenades;
+
+ if ( !cfg.per_type_colors )
+ {
+ return cfg.line_color;
+ }
+
+ switch ( weapon_hash )
+ {
+ case "weapon_hegrenade"_hash: return cfg.color_he;
+ case "weapon_flashbang"_hash: return cfg.color_flash;
+ case "weapon_smokegrenade"_hash: return cfg.color_smoke;
+ case "weapon_molotov"_hash:
+ case "weapon_incgrenade"_hash: return cfg.color_molotov;
+ case "weapon_decoy"_hash: return cfg.color_decoy;
+ default: return cfg.line_color;
+ }
+ }
+
void grenades::simulate( const math::vector3& start, const math::vector3& velocity, trajectory& out )
{
this->m_sv_gravity = systems::g_convars.get( CONVAR( "sv_gravity"_hash ) );
@@ -401,7 +422,7 @@ namespace features::misc {
{
out.end_tick = tick;
out.end_pos = pos;
- out.duration = static_cast< float >( tick ) * cstypes::tick_interval;
+ out.duration = static_cast< float >( tick ) * tick_interval;
break;
}
}
@@ -411,7 +432,7 @@ namespace features::misc {
{
out.end_tick = tick;
out.end_pos = pos;
- out.duration = static_cast< float >( tick ) * cstypes::tick_interval;
+ out.duration = static_cast< float >( tick ) * tick_interval;
break;
}
@@ -435,13 +456,13 @@ namespace features::misc {
void grenades::step_simulation( math::vector3& pos, math::vector3& vel, systems::bvh::trace_result& trace )
{
const auto gravity = this->m_sv_gravity * gravity_scale;
- const auto new_vel_z = vel.z - gravity * cstypes::tick_interval;
+ const auto new_vel_z = vel.z - gravity * tick_interval;
const math::vector3 move
{
- vel.x * cstypes::tick_interval,
- vel.y * cstypes::tick_interval,
- ( vel.z + new_vel_z ) * 0.5f * cstypes::tick_interval
+ vel.x * tick_interval,
+ vel.y * tick_interval,
+ ( vel.z + new_vel_z ) * 0.5f * tick_interval
};
vel.z = new_vel_z;
@@ -487,7 +508,7 @@ namespace features::misc {
const auto remaining = 1.0f - trace.fraction;
if ( remaining > 0.0f )
{
- const auto post_trace = systems::g_bvh.trace_ray( pos, pos + new_vel * ( remaining * cstypes::tick_interval ) );
+ const auto post_trace = systems::g_bvh.trace_ray( pos, pos + new_vel * ( remaining * tick_interval ) );
pos = post_trace.end_pos;
}
}
@@ -500,24 +521,24 @@ namespace features::misc {
case "weapon_decoy"_hash:
{
const auto speed_2d = std::sqrtf( vel.x * vel.x + vel.y * vel.y );
- const auto check_ticks = static_cast< int >( 0.2f / cstypes::tick_interval );
+ const auto check_ticks = static_cast< int >( 0.2f / tick_interval );
return speed_2d < this->m_velocity_threshold && ( tick % check_ticks ) == 0;
}
case "weapon_molotov"_hash:
case "weapon_incgrenade"_hash:
- return static_cast< float >( tick ) * cstypes::tick_interval > this->m_detonate_time;
+ return static_cast< float >( tick ) * tick_interval > this->m_detonate_time;
case "weapon_flashbang"_hash:
case "weapon_hegrenade"_hash:
- return static_cast< float >( tick - 8 ) * cstypes::tick_interval > this->m_detonate_time;
+ return static_cast< float >( tick - 8 ) * tick_interval > this->m_detonate_time;
default:
return false;
}
}
- void grenades::render_trajectory( zdraw::draw_list& draw_list, const trajectory& traj, float alpha ) const
+ void grenades::render_trajectory( zdraw::draw_list& draw_list, const trajectory& traj, float alpha, std::uintptr_t weapon_hash ) const
{
if ( !traj.valid || traj.points.size( ) < 2 )
{
@@ -525,6 +546,7 @@ namespace features::misc {
}
const auto& cfg = settings::g_misc.m_grenades;
+ const auto base_color = this->color_for_type( weapon_hash );
const auto total = traj.points.size( );
for ( std::size_t i = 0; i + 1 < total; ++i )
@@ -538,24 +560,30 @@ namespace features::misc {
}
const auto t = static_cast< float >( i ) / static_cast< float >( total - 1 );
- const auto seg_alpha = alpha * ( 1.0f - t * 0.6f );
- const auto a = static_cast< std::uint8_t >( std::clamp( seg_alpha * static_cast< float >( cfg.color.a ), 0.0f, 255.0f ) );
+ const auto seg_alpha = cfg.line_gradient ? alpha * ( 1.0f - t * 0.6f ) : alpha;
+ const auto a = static_cast< std::uint8_t >( std::clamp( seg_alpha * static_cast< float >( base_color.a ), 0.0f, 255.0f ) );
- draw_list.add_line( s0.x, s0.y, s1.x, s1.y, { cfg.color.r, cfg.color.g, cfg.color.b, a }, 2.0f );
+ draw_list.add_line( s0.x, s0.y, s1.x, s1.y, zdraw::rgba{ base_color.r, base_color.g, base_color.b, a }, cfg.line_thickness );
}
- for ( const auto& bounce : traj.bounces )
+ if ( cfg.show_bounces )
{
- const auto s = systems::g_view.project( bounce );
- if ( !systems::g_view.projection_valid( s ) )
+ const auto sz = cfg.bounce_size;
+ const auto outline_sz = sz + 1.0f;
+
+ for ( const auto& bounce : traj.bounces )
{
- continue;
- }
+ const auto s = systems::g_view.project( bounce );
+ if ( !systems::g_view.projection_valid( s ) )
+ {
+ continue;
+ }
- const auto a = static_cast< std::uint8_t >( alpha * static_cast< float >( cfg.color.a ) );
+ const auto a = static_cast< std::uint8_t >( alpha * static_cast< float >( cfg.bounce_color.a ) );
- draw_list.add_rect_filled( s.x - 3.0f, s.y - 3.0f, 6.0f, 6.0f, { 0, 0, 0, a } );
- draw_list.add_rect_filled( s.x - 2.0f, s.y - 2.0f, 4.0f, 4.0f, { cfg.color.r, cfg.color.g, cfg.color.b, a } );
+ draw_list.add_rect_filled( s.x - outline_sz, s.y - outline_sz, outline_sz * 2.0f, outline_sz * 2.0f, zdraw::rgba{ 0, 0, 0, a } );
+ draw_list.add_rect_filled( s.x - sz, s.y - sz, sz * 2.0f, sz * 2.0f, zdraw::rgba{ cfg.bounce_color.r, cfg.bounce_color.g, cfg.bounce_color.b, a } );
+ }
}
if ( traj.end_tick >= 0 )
@@ -563,10 +591,12 @@ namespace features::misc {
const auto s = systems::g_view.project( traj.end_pos );
if ( systems::g_view.projection_valid( s ) )
{
- const auto a = static_cast< std::uint8_t >( alpha * static_cast< float >( cfg.color.a ) );
+ const auto sz = cfg.detonate_size;
+ const auto outline_sz = sz + 1.0f;
+ const auto a = static_cast< std::uint8_t >( alpha * static_cast< float >( cfg.detonate_color.a ) );
- draw_list.add_rect_filled( s.x - 5.0f, s.y - 5.0f, 10.0f, 10.0f, { 0, 0, 0, a } );
- draw_list.add_rect_filled( s.x - 4.0f, s.y - 4.0f, 8.0f, 8.0f, { cfg.color.r, cfg.color.g, cfg.color.b, a } );
+ draw_list.add_rect_filled( s.x - outline_sz, s.y - outline_sz, outline_sz * 2.0f, outline_sz * 2.0f, zdraw::rgba{ 0, 0, 0, a } );
+ draw_list.add_rect_filled( s.x - sz, s.y - sz, sz * 2.0f, sz * 2.0f, zdraw::rgba{ cfg.detonate_color.r, cfg.detonate_color.g, cfg.detonate_color.b, a } );
}
}
}
diff --git a/catalyst/project/core/features/impl/misc/impacts.cpp b/catalyst/project/core/features/impl/misc/impacts.cpp
index 0df49dd..70e30b4 100644
--- a/catalyst/project/core/features/impl/misc/impacts.cpp
+++ b/catalyst/project/core/features/impl/misc/impacts.cpp
@@ -1 +1,114 @@
-#include
\ No newline at end of file
+#include
+
+namespace features::misc {
+
+ void impacts::on_render( zdraw::draw_list& draw_list )
+ {
+ const auto& cfg = settings::g_esp.m_bullet_tracers;
+ if ( !cfg.enabled )
+ {
+ return;
+ }
+
+ const auto& view_matrix = systems::g_view.matrix( );
+
+ auto get_w = [&]( const math::vector3& p ) {
+ return view_matrix[ 3 ][ 0 ] * p.x + view_matrix[ 3 ][ 1 ] * p.y + view_matrix[ 3 ][ 2 ] * p.z + view_matrix[ 3 ][ 3 ];
+ };
+
+ for ( const auto& tracer : this->m_tracers )
+ {
+ math::vector3 p1 = tracer.start;
+ math::vector3 p2 = tracer.end;
+
+ float w1 = get_w( p1 );
+ float w2 = get_w( p2 );
+
+ // Basic Near Plane Clipping (w = 0.01)
+ if ( w1 < 0.01f && w2 < 0.01f ) continue;
+
+ if ( w1 < 0.01f ) {
+ float t = ( 0.01f - w1 ) / ( w2 - w1 );
+ p1 = p1 + ( p2 - p1 ) * t;
+ }
+ else if ( w2 < 0.01f ) {
+ float t = ( 0.01f - w1 ) / ( w2 - w1 );
+ p2 = p1 + ( p2 - p1 ) * t;
+ }
+
+ const auto s1 = systems::g_view.project( p1 );
+ const auto s2 = systems::g_view.project( p2 );
+
+ if ( systems::g_view.projection_valid( s1 ) && systems::g_view.projection_valid( s2 ) )
+ {
+ const auto alpha_frac = std::clamp( tracer.time / tracer.max_time, 0.0f, 1.0f );
+ const auto alpha = static_cast< std::uint8_t >( alpha_frac * cfg.color.a );
+ const auto color = zdraw::rgba{ cfg.color.r, cfg.color.g, cfg.color.b, alpha };
+
+ // Main tracer line
+ draw_list.add_line( s1.x, s1.y, s2.x, s2.y, color, cfg.thickness );
+
+ // Optional: subtle glow effect by drawing a thinner line on top
+ if ( alpha > 50 )
+ {
+ draw_list.add_line( s1.x, s1.y, s2.x, s2.y, zdraw::rgba{ 255, 255, 255, static_cast( alpha / 2 ) }, cfg.thickness * 0.5f );
+ }
+ }
+ }
+ }
+
+ void impacts::tick( )
+ {
+ const auto& cfg = settings::g_esp.m_bullet_tracers;
+ const auto pawn = systems::g_local.pawn( );
+
+ if ( !pawn )
+ {
+ this->m_tracers.clear( );
+ this->m_old_shots = 0;
+ return;
+ }
+
+ const auto shots_fired = g::memory.read( pawn + SCHEMA( "C_CSPlayerPawn", "m_iShotsFired"_hash ) );
+
+ if ( shots_fired > this->m_old_shots )
+ {
+ const auto view_origin = systems::g_view.origin( );
+ const auto angles = systems::g_view.angles( );
+
+ math::vector3 forward{}, right{}, up{};
+ angles.to_directions( &forward, &right, &up );
+
+ // Offset the start point slightly to the right and down to look like it comes from the gun muzzle (fake muzzle)
+ const auto start = view_origin + ( right * 4.0f ) + ( up * -3.0f ) + ( forward * 10.0f );
+ const auto trace_end = view_origin + forward * 8192.0f;
+
+ // Use BVH to find the actual hit location
+ const auto trace = systems::g_bvh.trace_ray( view_origin, trace_end );
+ const auto end = trace.hit ? trace.end_pos : trace_end;
+
+ this->m_tracers.push_back( { start, end, static_cast(cfg.duration), static_cast(cfg.duration) } );
+ }
+ this->m_old_shots = shots_fired;
+
+ // Stabilization: use real time instead of frames
+ static auto last_time = std::chrono::steady_clock::now( );
+ const auto now = std::chrono::steady_clock::now( );
+ const auto dt = std::chrono::duration( now - last_time ).count( );
+ last_time = now;
+
+ for ( auto it = this->m_tracers.begin( ); it != this->m_tracers.end( ); )
+ {
+ it->time -= dt;
+ if ( it->time <= 0.0f )
+ {
+ it = this->m_tracers.erase( it );
+ }
+ else
+ {
+ ++it;
+ }
+ }
+ }
+
+} // namespace features::misc
diff --git a/catalyst/project/core/features/impl/misc/misc.cpp b/catalyst/project/core/features/impl/misc/misc.cpp
new file mode 100644
index 0000000..07fe91f
--- /dev/null
+++ b/catalyst/project/core/features/impl/misc/misc.cpp
@@ -0,0 +1,411 @@
+#include
+#include "sounds.h"
+#include
+#pragma comment(lib, "winmm.lib")
+
+namespace features::misc {
+
+ void misc_features::on_render( zdraw::draw_list& draw_list )
+ {
+ std::unique_lock lock( this->m_mutex );
+ this->draw_watermark( draw_list );
+ this->draw_spectators( draw_list );
+ this->draw_bomb_timer( draw_list );
+ this->draw_hit_markers( draw_list );
+ this->draw_damage_indicators( draw_list );
+ }
+
+ void misc_features::tick_write( )
+ {
+ this->fov_changer( );
+ }
+
+ void misc_features::tick( )
+ {
+ this->hitsounds( );
+
+ const auto now = std::chrono::steady_clock::now( );
+ std::unique_lock lock( this->m_mutex );
+
+ for ( const auto& player : systems::g_collector.players( ) )
+ {
+ if ( !systems::g_local.is_enemy( player.team ) )
+ {
+ m_health_history.erase( player.pawn );
+ continue;
+ }
+
+ auto& history = m_health_history[ player.pawn ];
+
+ // If first time seeing them, just set history and skip damage check
+ if ( history.health == -1 )
+ {
+ history.health = player.health;
+ history.origin = player.origin;
+ continue;
+ }
+
+ if ( player.health < history.health )
+ {
+ if ( settings::g_esp.m_damage_indicator.enabled )
+ {
+ // Show damage indicator
+ m_damage_indicators.push_back({
+ player.origin + math::vector3{ 0.0f, 0.0f, 40.0f },
+ static_cast( history.health - player.health ),
+ false, // Todo: check hitgroup for headshot
+ now,
+ static_cast( settings::g_esp.m_damage_indicator.duration ) / 1000.0f
+ });
+ }
+ }
+
+ if ( player.health <= 0 )
+ {
+ m_health_history.erase( player.pawn );
+ }
+ else
+ {
+ history.health = player.health;
+ history.origin = player.origin;
+ }
+ }
+ }
+
+ void misc_features::fov_changer()
+ {
+ if (!settings::g_misc.m_fov_changer.enabled)
+ return;
+
+ const auto local_pawn = systems::g_local.pawn();
+ if (!local_pawn)
+ return;
+
+ const auto camera_services = g::memory.read(
+ local_pawn + SCHEMA("C_BasePlayerPawn", "m_pCameraServices"_hash)
+ );
+ if (!camera_services)
+ return;
+
+ // Optional: skip when scoped (common preference)
+ const bool is_scoped = g::memory.read(
+ local_pawn + SCHEMA("C_CSPlayerPawn", "m_bIsScoped"_hash)
+ );
+
+ if (is_scoped && settings::g_misc.m_fov_changer.disable_when_scoped)
+ return;
+
+ const auto target_fov = static_cast(settings::g_misc.m_fov_changer.fov);
+ const auto target_viewmodel_fov = static_cast(settings::g_misc.m_fov_changer.viewmodel_fov);
+
+ const auto fov_offset = SCHEMA("CCSPlayerBase_CameraServices", "m_iFOV"_hash);
+ const auto fov = g::memory.read(camera_services + fov_offset);
+ g::memory.write(camera_services + fov_offset, target_fov);
+ }
+
+ void misc_features::draw_spectators( zdraw::draw_list& draw_list )
+ {
+ if ( !settings::g_misc.m_main.spectator_list )
+ return;
+
+ std::vector specs{};
+
+ const auto local_controller = systems::g_local.controller( );
+ if ( !local_controller )
+ return;
+
+ for ( const auto& entry : systems::g_entities.by_type( systems::entities::type::player ) )
+ {
+ const auto controller = entry.ptr;
+ if ( !controller || controller == local_controller )
+ continue;
+
+ const auto alive = g::memory.read( controller + SCHEMA( "CCSPlayerController", "m_bPawnIsAlive"_hash ) );
+ if ( alive )
+ continue;
+
+ // Priority to observer pawn as that's what spectators use
+ auto pawn_handle = g::memory.read( controller + SCHEMA( "CCSPlayerController", "m_hObserverPawn"_hash ) );
+ if ( !pawn_handle || pawn_handle == 0xffffffff )
+ pawn_handle = g::memory.read( controller + SCHEMA( "CCSPlayerController", "m_hPlayerPawn"_hash ) );
+
+ if ( !pawn_handle || pawn_handle == 0xffffffff )
+ continue;
+
+ const auto pawn = systems::g_entities.lookup( pawn_handle );
+ if ( !pawn )
+ continue;
+
+ const auto observer_services = g::memory.read( pawn + SCHEMA( "C_BasePlayerPawn", "m_pObserverServices"_hash ) );
+ if ( !observer_services )
+ continue;
+
+ const auto observer_target_handle = g::memory.read( observer_services + SCHEMA( "CPlayer_ObserverServices", "m_hObserverTarget"_hash ) );
+ if ( !observer_target_handle || observer_target_handle == 0xffffffff )
+ continue;
+
+ const auto observer_target_pawn = systems::g_entities.lookup( observer_target_handle );
+ if ( !observer_target_pawn || observer_target_pawn != systems::g_local.view_pawn( ) )
+ continue;
+
+ const auto name_ptr = g::memory.read( controller + SCHEMA( "CCSPlayerController", "m_sSanitizedPlayerName"_hash ) );
+ if ( name_ptr )
+ {
+ const auto name = g::memory.read_string( name_ptr, 128 );
+ const auto mode = g::memory.read( observer_services + SCHEMA( "CPlayer_ObserverServices", "m_iObserverMode"_hash ) );
+
+ if ( mode == 0 || mode == 1 )
+ continue; // Mode 0 = None, Mode 1 = Deathcam
+
+ std::string mode_str;
+ switch (mode) {
+ case 2: mode_str = "freezecam"; break;
+ case 4: mode_str = "freecam"; break;
+ case 5: mode_str = "first-person"; break;
+ case 6: mode_str = "third-person"; break;
+ default: mode_str = std::to_string(mode); break;
+ }
+ specs.push_back(std::format("{} ({})", name, mode_str));
+ }
+ }
+
+ const auto display = zdraw::get_display_size( );
+ const auto x = 10.0f;
+ auto y = display.second / 2.0f;
+
+ const auto& main_cfg = settings::g_misc.m_main;
+
+ draw_list.add_text( x, y, "spectators", zdraw::get_font( ), main_cfg.spectator_list_color, zdraw::text_style::outlined );
+ y += 15.0f;
+
+ for ( const auto& spec : specs )
+ {
+ draw_list.add_text( x, y, spec, zdraw::get_font( ), zdraw::rgba{ 195, 200, 215, 230 }, zdraw::text_style::outlined );
+ y += 15.0f;
+ }
+ }
+
+ void misc_features::draw_bomb_timer( zdraw::draw_list& draw_list )
+ {
+ if ( !settings::g_misc.m_main.bomb_timer )
+ return;
+
+ const auto& main_cfg = settings::g_misc.m_main;
+
+ for ( const auto& bomb : systems::g_entities.by_type( systems::entities::type::bomb ) )
+ {
+ const auto class_name_ptr = g::memory.read