Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Исходник [Сурс] Rainbow Six Siege — Математика отдачи (DPI Independent + ADS Multipliers)

Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
617
Реакции
16
Если ваша паста для Осады до сих пор игнорирует XFactor и кратность прицелов, а вы просто подбираете константы под DPI — земля вам пухом. В R6 чувствительность в прицеле (ADS) работает через систему множителей, и нормальный скрипт должен это обсчитывать на лету.

Нашел на просторах годную базу для тех, кто хочет сделать нормальный recoil-engine на Python. Основная фишка здесь — полная независимость от DPI. Движок игры сам считает перемещение на основе игровых настроек из GameSettings.ini.

Что умеет этот модуль:
  1. Считает задержку между выстрелами строго по RPM (rounds per minute).
  2. Корректно нормализует ADS sensitivity для всех типов оптики (от 1.0x до 12.0x).
  3. Учитывает FOV игрока для масштабирования движения мыши.
  4. Рассчитывает тайминги для макросов (актуально для тех, кто до сих пор юзает логику Logitech).

Код:
Expand Collapse Copy
"""
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 для всех актуальных стволов в игре. Полезно, чтобы не парсить память на предмет скорострельности, а просто брать готовую константу.

Код:
Expand Collapse Copy
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 часто мониторится, а паттерны движения выдают эмуляцию.

Как улучшить:
  1. Забудьте про голый ctypes. Посмотрите в сторону работы с драйвером (Interception — под вопросом, лучше кастомный или через Arduino/Raspberry).
  2. Добавляйте рандомизацию (jitter) не только в координаты, но и в shot_delay. Идеально ровные паузы — палево.
  3. Интегрируйте чтение конфига игры напрямую из папки документов, чтобы подхватывать MouseYaw и XFactorAiming автоматом.

Если кто-то уже юзает похожую математику с внешним драйвером — отпишитесь по поводу флагов BattlEye на чтение RPM через дескриптор.

Интересно будет посмотреть, как этот движок поведет себя с обновленной отдачей на щитах.
 
Назад
Сверху Снизу