- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 598
- Reaction score
- 7
Standard recoil scripts usually fall flat because they ignore how Siege actually handles sensitivity variables.
Most pastes just try to move the mouse by fixed pixel counts, which breaks the moment you change your FOV or swap a 1.0x for a 2.5x. This engine logic handles the actual math behind the GameSettings.ini, making the compensation DPI independent by normalizing everything back to hipfire equivalents.
Technical Breakdown
The Engine Logic
Stealth and Prevention
If you're using ctypes.windll.user32.mouse_event, you're playing with fire on anything higher than a casual match. BattlEye flags synthetic mouse input patterns through several heuristics. To keep your account alive:
Anyone tested if the recent shadow changes to the XFactor multiplier in the config files broke this math for the higher magnification scopes?
Most pastes just try to move the mouse by fixed pixel counts, which breaks the moment you change your FOV or swap a 1.0x for a 2.5x. This engine logic handles the actual math behind the GameSettings.ini, making the compensation DPI independent by normalizing everything back to hipfire equivalents.
Technical Breakdown
- RPM to MS Delay: Siege weapons have fixed fire rates. A simple 60000 / RPM gives you the exact delay between shots. Using generic 100ms waits is a fast way to get out-transfers in a gunfight.
- ADS Normalization: The engine calculates a multiplier based on the XFactorAiming and specific zoom factors (ranging from 0.6 for 1.0x to 0.32 for 5.0x).
- DPI Independence: Since R6 recoil is proportional to in-game sensitivity values, the script calculates deltas relative to your MouseYaw and MousePitch, meaning it doesn't care if you're on 400 or 1600 DPI.
Essential fire rate values for Attackers and Defenders:
- R4-C: 860 RPM
- F2 (Twitch): 980 RPM
- AK-12: 850 RPM
- Vector .45 ACP: 1200 RPM
- Scorpion EVO 3: 1080 RPM
- DP27 (Tachanka): 550 RPM (Uses a 12ms timing constant)
- R4-C: 860 RPM
- F2 (Twitch): 980 RPM
- AK-12: 850 RPM
- Vector .45 ACP: 1200 RPM
- Scorpion EVO 3: 1080 RPM
- DP27 (Tachanka): 550 RPM (Uses a 12ms timing constant)
The Engine Logic
Code:
"""
Enhanced Recoil Engine - Uses R6 GameSettings ADS Sensitivity
Calculates weapon-specific timing based on RPM
"""
import math
from typing import Tuple
class EnhancedRecoilEngine:
"""
Recoil compensation engine that properly accounts for:
- Per-scope ADS sensitivity
- Weapon fire rate (RPM → MS delay)
- FOV and resolution
- Custom multipliers from GameSettings
NOTE: R6 Siege recoil is DPI INDEPENDENT - only in-game sensitivity matters!
"""
def __init__(self, game_settings):
"""
Args:
game_settings: R6GameSettings object
"""
self.game_settings = game_settings
# Current weapon state
self.current_weapon = None
self.current_scope = "1.0x"
self.current_rpm = 800 # Default RPM
# Spray state
self.shot_count = 0
self.max_shots = 30
print(f"[Recoil] Initialized - DPI Independent Mode")
print(f"[Recoil] FOV: {game_settings.fov}")
print(f"[Recoil] Mouse Yaw (H): {game_settings.mouse_yaw}")
print(f"[Recoil] Mouse Pitch (V): {game_settings.mouse_pitch}")
print(f"[Recoil] ADS 1x: {game_settings.ads_1x}")
def set_weapon(self, weapon_name, scope="1.0x", rpm=800):
"""
Set current weapon and scope
Args:
weapon_name: Weapon identifier
scope: Scope magnification ("1.0x", "1.5x", "2.0x", "2.5x", etc)
rpm: Weapon fire rate in rounds per minute
"""
self.current_weapon = weapon_name
self.current_scope = scope
self.current_rpm = rpm
self.shot_count = 0
print(f"[Recoil] Set weapon: {weapon_name} | {scope} | {rpm} RPM")
def get_shot_delay(self):
"""
Calculate delay between shots based on weapon RPM
Formula: delay_ms = 60000 / RPM
Returns:
float: Delay in seconds
"""
if self.current_rpm <= 0:
return 0.1 # Default 100ms
# Convert RPM to delay in milliseconds
delay_ms = 60000 / self.current_rpm
# Convert to seconds
delay_seconds = delay_ms / 1000.0
return delay_seconds
def get_timing_constant(self):
"""
Get timing constant in MS (used by Logitech scripts)
Formula: timing_ms = RPM / 6000
Most guns: 5-8 ms
Outliers: DP27 = 12ms, C1 9mm = 12ms
Returns:
int: Timing constant in milliseconds
"""
timing = int(self.current_rpm / 100)
# Clamp to reasonable range
return max(5, min(12, timing))
def calculate_ads_multiplier(self):
"""
Calculate ADS sensitivity multiplier for current scope
Uses R6 formula to normalize ADS back to hipfire:
multiplier = (ads_value / zoom_factor) * XFactor * zoom * hipfire
Returns:
float: Multiplier to apply to recoil values
"""
# Get raw ADS sensitivity for current scope
ads_value = self.game_settings.get_ads_sens_for_scope(self.current_scope)
# Zoom levels
zoom_levels = {
"1.0x": 0.6,
"1.5x": 0.5,
"2.0x": 0.45,
"2.5x": 0.42,
"3.0x": 0.38,
"4.0x": 0.35,
"5.0x": 0.32,
"12.0x": 0.20
}
zoom = zoom_levels.get(self.current_scope, 0.6)
fov = self.game_settings.fov
# Calculate zoom factor
fov_rad = fov * math.pi / 180
zoom_factor = (zoom / math.tan((zoom * fov_rad) / 2)) / math.tan(fov_rad / 2)
# Normalize ADS to hipfire equivalent
normalized = (ads_value / zoom_factor) * self.game_settings.xfactor_aiming * zoom
# Compare to hipfire (use MouseYaw as the primary sensitivity)
# R6 uses MouseYaw for horizontal, MousePitch for vertical
# For recoil (mostly vertical), we use the average or just Yaw
hipfire = self.game_settings.mouse_yaw * self.game_settings.mouse_xfactor
# Multiplier
multiplier = normalized / hipfire if hipfire > 0 else 1.0
return multiplier
def calculate_fov_multiplier(self):
"""
Calculate FOV-based multiplier
Returns:
float: FOV multiplier (normalized to 75 FOV baseline)
"""
return self.game_settings.fov / 75.0
def get_total_multiplier(self):
"""
Get complete multiplier combining all factors
R6 Siege is DPI independent - only ADS and FOV matter
Returns:
float: Final multiplier for recoil compensation
"""
ads_mult = self.calculate_ads_multiplier()
fov_mult = self.calculate_fov_multiplier()
total = ads_mult * fov_mult
return total
def apply_recoil_compensation(self, base_horizontal, base_vertical):
"""
Apply all multipliers to base recoil values
Args:
base_horizontal: Base horizontal recoil
base_vertical: Base vertical recoil
Returns:
Tuple[float, float]: (compensated_h, compensated_v)
"""
multiplier = self.get_total_multiplier()
comp_h = base_horizontal * multiplier
comp_v = base_vertical * multiplier
return (comp_h, comp_v)
def get_recoil_for_shot(self, shot_number, pattern):
"""
Get recoil compensation for specific shot in pattern
Args:
shot_number: Current shot number (0-indexed)
pattern: List of (h, v) tuples for recoil pattern
Returns:
Tuple[float, float]: (h, v) compensated recoil
"""
# Get base recoil from pattern
if shot_number >= len(pattern):
# Use last value if past pattern end
base_h, base_v = pattern[-1]
else:
base_h, base_v = pattern[shot_number]
# Apply all multipliers
return self.apply_recoil_compensation(base_h, base_v)
def reset_spray(self):
"""Reset shot counter for new spray"""
self.shot_count = 0
def get_recoil_compensation(self):
"""
Get recoil compensation for current shot (auto-increment)
Uses generic pattern if no weapon set
Returns:
Tuple[float, float]: (horizontal, vertical) mouse movement
"""
# Generic recoil pattern (vertical only, no horizontal drift)
generic_pattern = [
(0, 8), # Shot 1
(0, 10), # Shot 2
(0, 12), # Shot 3
(0, 11), # Shot 4
(0, 10), # Shot 5
(0, 9), # Shot 6
(0, 9), # Shot 7
(0, 8), # Shot 8
(0, 8), # Shot 9
(0, 7), # Shot 10
(0, 7), # Shot 11-30 (rest of mag)
]
# Get compensation for current shot
compensation = self.get_recoil_for_shot(self.shot_count, generic_pattern)
# Increment shot counter
self.shot_count += 1
# Cap at reasonable number
if self.shot_count > 50:
self.shot_count = 50
return compensation
def get_next_compensation(self, pattern):
"""
Get compensation for next shot and increment counter
Args:
pattern: Recoil pattern as list of (h, v) tuples
Returns:
Tuple[float, float]: (h, v) compensated recoil
"""
compensation = self.get_recoil_for_shot(self.shot_count, pattern)
self.shot_count += 1
return compensation
def get_status(self):
"""Get current recoil engine status"""
return {
'weapon': self.current_weapon or "None",
'scope': self.current_scope,
'rpm': self.current_rpm,
'shot_delay_ms': self.get_shot_delay() * 1000,
'ads_multiplier': self.calculate_ads_multiplier(),
'fov_multiplier': self.calculate_fov_multiplier(),
'total_multiplier': self.get_total_multiplier()
}
# Example weapon database with RPM
WEAPON_RPM = {
# Attackers
"R4-C": 860,
"G36C": 780,
"556xi": 690,
"F2": 980,
"417": 450, # DMR
"AK-12": 850,
"C8-SFW": 837,
"MK17 CQB": 585,
"CAMRS": 450, # DMR
"SR-25": 450, # DMR
"ARX200": 850,
"AR-15.50": 450, # DMR
"PDW9": 800,
"AUG A2": 720,
"COMMANDO 9": 780,
"C7E": 800,
"M762": 730,
"V308": 700,
"SPEAR .308": 700,
"Mk 14 EBR": 450, # DMR
"BOSG.12.2": 500, # Shotgun
"Type-89": 850,
"AR33": 749,
"G8A1": 850,
"AUG A3": 720,
"552 Commando": 690,
"L85A2": 670,
"LMG-E": 720,
"6P41": 680,
"AK-74M": 650,
# Defenders
"MP5": 800,
"MP5K": 800,
"UMP45": 600,
"FMG-9": 800,
"MP5SD": 800,
"P90": 970,
"9x19VSN": 750,
"MP7": 900,
"M12": 550,
"MPX": 830,
"M870": 100, # Shotgun
"VECTOR .45 ACP": 1200,
"T-5 SMG": 900,
"SCORPION EVO 3 A1": 1080,
"K1A": 720,
"Mx4 Storm": 750,
"ACS12": 300, # Auto Shotgun
"ALDA 5.56": 900,
"TCSG12": 450, # Slug Shotgun
"AUG A3": 720,
"COMMANDO 9": 780,
"DP27": 550, # Lord Tachanka (12ms timing)
"C1 9mm": 575, # Frost (12ms timing)
"9mm C1": 575,
"M590A1": 85, # Shotgun
"SG-CQB": 85, # Shotgun
"SASG-12": 330, # Auto Shotgun
"ITA12L": 85, # Shotgun
"SPAS-12": 200, # Semi-auto Shotgun
"SPAS-15": 290, # Auto Shotgun
"SIX12": 200, # Revolver Shotgun
"SIX12 SD": 200, # Suppressed
"SUPER 90": 200, # Shotgun
"M1014": 200, # Shotgun
"FO-12": 300, # Auto Shotgun
}
if __name__ == "__main__":
print("="*60)
print("ENHANCED RECOIL ENGINE TEST")
print("="*60)
# Import game settings
from game_settings_parser import R6GameSettings
settings = R6GameSettings()
engine = EnhancedRecoilEngine(settings) # No DPI parameter!
# Test with R4-C
print("\nTesting R4-C...")
r4c_rpm = WEAPON_RPM.get("R4-C", 860)
engine.set_weapon("R4-C", "1.0x", r4c_rpm)
# Show status
status = engine.get_status()
print("\nEngine Status:")
for key, value in status.items():
if isinstance(value, float):
print(f" {key:20s}: {value:.4f}")
else:
print(f" {key:20s}: {value}")
# Test recoil compensation
print("\nRecoil Compensation Test:")
test_pattern = [(0, 10), (0, 12), (-2, 11), (3, 10)]
for i, (base_h, base_v) in enumerate(test_pattern):
comp_h, comp_v = engine.get_recoil_for_shot(i, test_pattern)
print(f" Shot {i+1}: Base({base_h:3.0f}, {base_v:3.0f}) → Comp({comp_h:6.2f}, {comp_v:6.2f})")
# Test different scopes
print("\nScope Comparison:")
for scope in ["1.0x", "1.5x", "2.0x", "2.5x"]:
engine.set_weapon("R4-C", scope, r4c_rpm)
mult = engine.get_total_multiplier()
print(f" {scope}: Total multiplier = {mult:.4f}")
print("\n" + "="*60)
Stealth and Prevention
If you're using ctypes.windll.user32.mouse_event, you're playing with fire on anything higher than a casual match. BattlEye flags synthetic mouse input patterns through several heuristics. To keep your account alive:
- Ditch user32 for a kernel-level driver or use a signed Mouse/Keyboard interception library. Raw WinAPI calls are low-hanging fruit for AC.
- Add randomization: Introduce slight jitter to your sleep calls and pixel deltas. A perfectly static vertical line is an instant manual flag if you get reported.
- Resolution Check: The FOV multiplier must be normalized to a 75 FOV baseline to ensure consistency across different aspect ratios (4:3 vs 16:9).
Anyone tested if the recent shadow changes to the XFactor multiplier in the config files broke this math for the higher magnification scopes?