- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 617
- Реакции
- 16
Если ваша паста для Осады до сих пор игнорирует XFactor и кратность прицелов, а вы просто подбираете константы под DPI — земля вам пухом. В R6 чувствительность в прицеле (ADS) работает через систему множителей, и нормальный скрипт должен это обсчитывать на лету.
Нашел на просторах годную базу для тех, кто хочет сделать нормальный recoil-engine на Python. Основная фишка здесь — полная независимость от DPI. Движок игры сам считает перемещение на основе игровых настроек из GameSettings.ini.
Что умеет этот модуль:
В коде ниже — справочник RPM для всех актуальных стволов в игре. Полезно, чтобы не парсить память на предмет скорострельности, а просто брать готовую константу.
Советы по реверсу и Undetect-статусу:
В теме часто спрашивают, как сделать софт безопаснее. Использование ctypes.windll.user32.mouse_event в Осаде — это самый быстрый способ улететь от BattlEye. Флаг MOUSEEVENTF_ABSOLUTE часто мониторится, а паттерны движения выдают эмуляцию.
Как улучшить:
Если кто-то уже юзает похожую математику с внешним драйвером — отпишитесь по поводу флагов BattlEye на чтение RPM через дескриптор.
Интересно будет посмотреть, как этот движок поведет себя с обновленной отдачей на щитах.
Нашел на просторах годную базу для тех, кто хочет сделать нормальный recoil-engine на Python. Основная фишка здесь — полная независимость от DPI. Движок игры сам считает перемещение на основе игровых настроек из GameSettings.ini.
Что умеет этот модуль:
- Считает задержку между выстрелами строго по RPM (rounds per minute).
- Корректно нормализует ADS sensitivity для всех типов оптики (от 1.0x до 12.0x).
- Учитывает FOV игрока для масштабирования движения мыши.
- Рассчитывает тайминги для макросов (актуально для тех, кто до сих пор юзает логику Logitech).
Код:
"""
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)
В коде ниже — справочник RPM для всех актуальных стволов в игре. Полезно, чтобы не парсить память на предмет скорострельности, а просто брать готовую константу.
Код:
WEAPON_RPM = {
"R4-C": 860, "G36C": 780, "AK-12": 850, "F2": 980,
"MP5": 800, "VECTOR .45 ACP": 1200, "SCORPION EVO 3 A1": 1080,
"MP7": 900, "K1A": 720, "T-5 SMG": 900, "Mx4 Storm": 750
# Полный список в исходнике...
}
Советы по реверсу и Undetect-статусу:
В теме часто спрашивают, как сделать софт безопаснее. Использование ctypes.windll.user32.mouse_event в Осаде — это самый быстрый способ улететь от BattlEye. Флаг MOUSEEVENTF_ABSOLUTE часто мониторится, а паттерны движения выдают эмуляцию.
Как улучшить:
- Забудьте про голый ctypes. Посмотрите в сторону работы с драйвером (Interception — под вопросом, лучше кастомный или через Arduino/Raspberry).
- Добавляйте рандомизацию (jitter) не только в координаты, но и в shot_delay. Идеально ровные паузы — палево.
- Интегрируйте чтение конфига игры напрямую из папки документов, чтобы подхватывать MouseYaw и XFactorAiming автоматом.
Если кто-то уже юзает похожую математику с внешним драйвером — отпишитесь по поводу флагов BattlEye на чтение RPM через дескриптор.
Интересно будет посмотреть, как этот движок поведет себя с обновленной отдачей на щитах.