diff --git a/src/lang/ja.py b/src/lang/ja.py index 258e2ee..5e9c625 100644 --- a/src/lang/ja.py +++ b/src/lang/ja.py @@ -125,4 +125,41 @@ "言語を選択し、アプリを再起動して適用してください。", "Restart the app to apply the new language.": "新しい言語を適用するにはアプリを再起動してください。", + "Speed-based throttle": "速度ベースのスロットル", + "Extra force at standstill": "停止時の追加力", + "Fade-out speed (km/h)": "減衰速度 (km/h)", + "Extra resistance at low speed for precise control, lighter at high speed.": + "低速では精密操作のため抵抗を増し、高速では軽くします。", + "0 = off. Flat resistance added at 0 km/h, fading to normal.": + "0 = オフ。0 km/hで平坦な抵抗が追加され、通常まで減衰します。", + "Speed where boost fully fades. Default 80.": + "ブーストが完全に減衰する速度。デフォルト80。", + "Surface brake resistance": "路面ブレーキ抵抗", + "Tarmac multiplier": "ターマック乗数", + "Dirt multiplier": "ダート乗数", + "Gravel multiplier": "砂利乗数", + "Softer brake on loose surfaces, firmer on tarmac.": + "緩い路面ではブレーキが柔らかく、ターマックでは硬くなります。", + "1.0 = unchanged. Scales final resistance force.": + "1.0 = 変更なし。最終抵抗力をスケールします。", + "Lower = softer brake on dirt. Default 0.5.": + "低い = ダートでより柔らかいブレーキ。デフォルト0.5。", + "Lower = softer brake on gravel/water. Default 0.25.": + "低い = 砂利/水でより柔らかいブレーキ。デフォルト0.25。", + "Turbo lag": "ターボラグ", + "Turbo lag vibration": "ターボラグ振動", + "Rumble speed (Hz)": "振動速度 (Hz)", + "Rumble strength": "振動強度", + "Boost change sensitivity": "ブースト変化感度", + "Brief R2 rumble when boost pressure climbs.": + "ブースト圧力上昇時の短いR2振動。", + "Deep rumble frequency. Default 8.": + "低い振動周波数。デフォルト8。", + "Max vibration amplitude. Default 40.": + "最大振動振幅。デフォルト40。", + "Min boost delta per tick to trigger. Default 0.15.": + "トリガーするtickあたりの最小ブースト変化。デフォルト0.15。", + "Cooldown (ms)": "クールダウン (ms)", + "Min gap between rumbles. Suppresses steady-state jitter. Default 300.": + "振動間の最小間隔。定常状態のジッターを抑制します。デフォルト300。", } diff --git a/src/lang/ru.py b/src/lang/ru.py index 1d7e36e..7a166e4 100644 --- a/src/lang/ru.py +++ b/src/lang/ru.py @@ -136,4 +136,41 @@ "Выберите язык, затем перезапустите приложение, чтобы применить его.", "Restart the app to apply the new language.": "Перезапустите приложение, чтобы применить новый язык.", + "Speed-based throttle": "Зависимость газа от скорости", + "Extra force at standstill": "Доп. усилие на месте", + "Fade-out speed (km/h)": "Скорость затухания (км/ч)", + "Extra resistance at low speed for precise control, lighter at high speed.": + "Доп. сопротивление на низкой скорости для точного контроля, легче на высокой.", + "0 = off. Flat resistance added at 0 km/h, fading to normal.": + "0 = выкл. Плоское сопротивление при 0 км/ч, затухая до нормы.", + "Speed where boost fully fades. Default 80.": + "Скорость, где усиление полностью затухает. По умолчанию 80.", + "Surface brake resistance": "Поверхностное торможение", + "Tarmac multiplier": "Множитель асфальта", + "Dirt multiplier": "Множитель грязи", + "Gravel multiplier": "Множитель гравия", + "Softer brake on loose surfaces, firmer on tarmac.": + "Мягкий тормоз на рыхлых поверхностях, жёсткий на асфальте.", + "1.0 = unchanged. Scales final resistance force.": + "1.0 = без изменений. Масштабирует итоговое сопротивление.", + "Lower = softer brake on dirt. Default 0.5.": + "Ниже = мягче тормоз на грязи. По умолчанию 0.5.", + "Lower = softer brake on gravel/water. Default 0.25.": + "Ниже = мягче тормоз на гравии/воде. По умолчанию 0.25.", + "Turbo lag": "Турбояма", + "Turbo lag vibration": "Вибрация турбоямы", + "Rumble speed (Hz)": "Скорость гула (Гц)", + "Rumble strength": "Сила гула", + "Boost change sensitivity": "Чувствительность изменения буста", + "Brief R2 rumble when boost pressure climbs.": + "Краткая вибрация R2 при росте давления наддува.", + "Deep rumble frequency. Default 8.": + "Глубокая частота гула. По умолчанию 8.", + "Max vibration amplitude. Default 40.": + "Макс. амплитуда вибрации. По умолчанию 40.", + "Min boost delta per tick to trigger. Default 0.15.": + "Мин. дельта буста за тик для срабатывания. По умолчанию 0.15.", + "Cooldown (ms)": "Перезарядка (мс)", + "Min gap between rumbles. Suppresses steady-state jitter. Default 300.": + "Мин. пауза между вибрациями. Подавляет дрожание. По умолчанию 300.", } diff --git a/src/lang/tr.py b/src/lang/tr.py index beec0bb..c02d23f 100644 --- a/src/lang/tr.py +++ b/src/lang/tr.py @@ -125,4 +125,41 @@ "Bir dil seçin, ardından uygulamak için uygulamayı yeniden başlatın.", "Restart the app to apply the new language.": "Yeni dili uygulamak için uygulamayı yeniden başlatın.", + "Speed-based throttle": "Hıza dayalı gaz", + "Extra force at standstill": "Dururken ekstra kuvvet", + "Fade-out speed (km/h)": "Sönme hızı (km/s)", + "Extra resistance at low speed for precise control, lighter at high speed.": + "Düşük hızda hassas kontrol için ekstra direnç, yüksek hızda daha hafif.", + "0 = off. Flat resistance added at 0 km/h, fading to normal.": + "0 = kapalı. 0 km/s'de düz direnç eklenir, normale kadar sönür.", + "Speed where boost fully fades. Default 80.": + "Takviyenin tamamen söndüğü hız. Varsayılan 80.", + "Surface brake resistance": "Yüzey freni direnci", + "Tarmac multiplier": "Asfalt çarpanı", + "Dirt multiplier": "Toprak çarpanı", + "Gravel multiplier": "Çakıl çarpanı", + "Softer brake on loose surfaces, firmer on tarmac.": + "Gevşek yüzeylerde daha yumuşak fren, asfaltta daha sert.", + "1.0 = unchanged. Scales final resistance force.": + "1.0 = değişmez. Son direnç kuvvetini ölçekler.", + "Lower = softer brake on dirt. Default 0.5.": + "Düşük = toprakta daha yumuşak fren. Varsayılan 0.5.", + "Lower = softer brake on gravel/water. Default 0.25.": + "Düşük = çakıl/suda daha yumuşak fren. Varsayılan 0.25.", + "Turbo lag": "Turbo gecikmesi", + "Turbo lag vibration": "Turbo gecikme titreşimi", + "Rumble speed (Hz)": "Gürültü hızı (Hz)", + "Rumble strength": "Gürültü gücü", + "Boost change sensitivity": "Boost değişim hassasiyeti", + "Brief R2 rumble when boost pressure climbs.": + "Boost basıncı yükselirken kısa R2 gürültüsü.", + "Deep rumble frequency. Default 8.": + "Derin gürültü frekansı. Varsayılan 8.", + "Max vibration amplitude. Default 40.": + "Maks titreşim genliği. Varsayılan 40.", + "Min boost delta per tick to trigger. Default 0.15.": + "Tetiklemek için tick başına min boost deltası. Varsayılan 0.15.", + "Cooldown (ms)": "Bekleme (ms)", + "Min gap between rumbles. Suppresses steady-state jitter. Default 300.": + "Gürültüler arası min boşluk. Kararlı durum titremesini bastırır. Varsayılan 300.", } diff --git a/src/lang/zh.py b/src/lang/zh.py index b9db58d..702dd9a 100644 --- a/src/lang/zh.py +++ b/src/lang/zh.py @@ -125,4 +125,41 @@ "选择一种语言,然后重启应用以应用更改。", "Restart the app to apply the new language.": "重启应用以应用新语言。", + "Speed-based throttle": "基于速度的油门", + "Extra force at standstill": "静止时额外力度", + "Fade-out speed (km/h)": "衰减速度 (km/h)", + "Extra resistance at low speed for precise control, lighter at high speed.": + "低速时增加阻力以实现精确控制,高速时更轻。", + "0 = off. Flat resistance added at 0 km/h, fading to normal.": + "0 = 关闭。在0 km/h时增加平坦阻力,逐渐衰减至正常。", + "Speed where boost fully fades. Default 80.": + "增益完全衰减的速度。默认80。", + "Surface brake resistance": "路面刹车阻力", + "Tarmac multiplier": "柏油路倍率", + "Dirt multiplier": "泥土路倍率", + "Gravel multiplier": "碎石路倍率", + "Softer brake on loose surfaces, firmer on tarmac.": + "松散路面刹车更软,柏油路更硬。", + "1.0 = unchanged. Scales final resistance force.": + "1.0 = 不变。缩放最终阻力力度。", + "Lower = softer brake on dirt. Default 0.5.": + "越低 = 泥土路刹车越软。默认0.5。", + "Lower = softer brake on gravel/water. Default 0.25.": + "越低 = 碎石/水面刹车越软。默认0.25。", + "Turbo lag": "涡轮迟滞", + "Turbo lag vibration": "涡轮迟滞振动", + "Rumble speed (Hz)": "轰鸣速度 (Hz)", + "Rumble strength": "轰鸣强度", + "Boost change sensitivity": "增压变化灵敏度", + "Brief R2 rumble when boost pressure climbs.": + "增压压力上升时R2短暂轰鸣。", + "Deep rumble frequency. Default 8.": + "低沉轰鸣频率。默认8。", + "Max vibration amplitude. Default 40.": + "最大振动幅度。默认40。", + "Min boost delta per tick to trigger. Default 0.15.": + "每次触发最小增压变化量。默认0.15。", + "Cooldown (ms)": "冷却时间 (ms)", + "Min gap between rumbles. Suppresses steady-state jitter. Default 300.": + "振动间最短间隔。抑制稳态抖动。默认300。", } diff --git a/src/modules/config/settings.py b/src/modules/config/settings.py index 07908b0..20d8beb 100644 --- a/src/modules/config/settings.py +++ b/src/modules/config/settings.py @@ -33,6 +33,14 @@ class Settings: enable_handbrake_bonus: bool = True handbrake_bonus: int = 60 # flat extra force while handbrake is engaged + # MARK: L2 surface-type brake + # Modulates brake resistance based on surface type. + # Tarmac = firm, dirt = softer, gravel = softest. Off by default. + enable_surface_brake: bool = False + surface_brake_tarmac: float = 1.0 # multiplier on tarmac (1.0 = unchanged) + surface_brake_dirt: float = 0.5 # multiplier on dirt + surface_brake_gravel: float = 0.25 # multiplier on gravel/water + # MARK: L2 ABS pulse # Vibrates when tire slip crosses thresholds under hard braking. enable_abs: bool = True @@ -53,6 +61,13 @@ class Settings: throttle_wall_engage_at: int = 250 # byte that triggers firmware wall. DO NOT CHANGE throttle_wall_release_at: int = 200 # hysteresis exit byte. DO NOT CHANGE + # MARK: R2 speed-based throttle + # Extra throttle resistance at low speed; fades as speed rises. + # boost=0 means off (no extra force). Flat additive at 0 km/h, fading to normal by fade_km. + enable_speed_throttle: bool = True + speed_throttle_boost: int = 30 # extra force added at standstill (0 = off) + speed_throttle_fade_km: float = 80.0 # speed where boost fully fades + # MARK: R2 rev limiter # Vibrates when rpm/max_rpm exceeds the ratio; brief hold smooths rpm bounce. enable_rev_limiter: bool = True @@ -78,6 +93,15 @@ class Settings: idle_amp_high: int = 30 # loud half of the cycle idle_period_s: float = 0.5 # full cycle length (sec) + # MARK: R2 turbo lag + # Brief deep vibration on R2 when boost pressure is climbing, simulating + # turbo lag before full power kicks in. Silent at steady-state or zero boost. + enable_turbo_lag: bool = True + turbo_lag_freq: int = 8 # deep rumble frequency (Hz) + turbo_lag_amp: int = 40 # max vibration amplitude + turbo_lag_threshold: float = 0.15 # minimum boost change per tick to trigger + turbo_lag_cooldown_ms: float = 300.0 # min time between rumbles (suppresses jitter) + # MARK: Gear shift # One short burst on up/downshift while moving. enable_gear_shift: bool = True # buzz on R2 diff --git a/src/modules/forzahorizon/effects.py b/src/modules/forzahorizon/effects.py index b8b373f..a7bf280 100644 --- a/src/modules/forzahorizon/effects.py +++ b/src/modules/forzahorizon/effects.py @@ -68,6 +68,8 @@ def __init__(self): self._prev_gear = None self._shift_until = 0.0 self._rev_until = 0.0 + self._prev_boost = 0.0 + self._turbo_last_fire = 0.0 def arm_shift(self, t, s, now): gear = t["gear"] @@ -139,6 +141,21 @@ def wheelspin_buzz(self, t, s, now): return vibrate(45, min(255, int(amp * 2))) return vibrate(130, amp) # tarmac: sharp squeal + def turbo_lag(self, t, s, now): + if not s.enable_turbo_lag: + self._prev_boost = t["boost"] + return None + boost = t["boost"] + delta = boost - self._prev_boost + self._prev_boost = boost + if delta < s.turbo_lag_threshold or boost < 0.1: + return None + if now - self._turbo_last_fire < s.turbo_lag_cooldown_ms / 1000.0: + return None + self._turbo_last_fire = now + amp = min(255, int(s.turbo_lag_amp * min(delta / 0.2, 1.0))) + return vibrate(s.turbo_lag_freq, amp) + def abs_pulse(self, t, s): if not s.enable_abs: return None @@ -155,6 +172,19 @@ def brake_resistance(self, t, s): return rigid(s.handbrake_bonus) if handbrake else off() force = _ramp(t["brake"], s.brake_deadzone, s.brake_baseline_force, s.brake_max_force, s.brake_curve, s.brake_wall_engage_at) + if s.enable_surface_brake and t["brake"] > 0: + wheels = DRIVEN_WHEELS.get(t["drive_train"], ("fl", "fr", "rl", "rr")) + if any(t[f"wheel_in_puddle_{w}"] > 0 for w in wheels): + mult = s.surface_brake_gravel + else: + rumble = max(abs(t[f"surface_rumble_{w}"]) for w in wheels) + if rumble > 0.30: + mult = s.surface_brake_gravel + elif rumble > 0.10: + mult = s.surface_brake_dirt + else: + mult = s.surface_brake_tarmac + force = max(0, int(force * mult)) if handbrake: force += s.handbrake_bonus return rigid(force) @@ -162,8 +192,15 @@ def brake_resistance(self, t, s): def throttle_ramp(self, t, s): if not s.enable_throttle_resistance: return off() - return rigid(_ramp(t["accel"], s.accel_deadzone, s.throttle_baseline_force, - s.throttle_max_force, s.throttle_curve, s.throttle_wall_engage_at)) + force = _ramp(t["accel"], s.accel_deadzone, s.throttle_baseline_force, + s.throttle_max_force, s.throttle_curve, s.throttle_wall_engage_at) + if s.enable_speed_throttle and s.speed_throttle_boost > 0 and t["accel"] >= s.accel_deadzone: + fade = max(1.0, s.speed_throttle_fade_km) + speed = t["speed"] + if speed < fade: + ratio = speed / fade + force = min(255, force + int(s.speed_throttle_boost * (1.0 - ratio))) + return rigid(force) # --- Controller ----------------------------------------------------------- @@ -185,8 +222,9 @@ class Controller: 1. Gear shift thump - one-shot burst on every shift, brief 2. Rev limiter buzz - rpm/max_rpm >= rev_limit_ratio 3. Wheelspin buzz - driven wheels slipping (surface-aware) - 4. Firmware end wall - hard wall near 100% travel (hysteresis) - 5. Throttle resistance - default rigid ramp 0..max_force + 4. Turbo lag - boost pressure rising, deep rumble + 5. Firmware end wall - hard wall near 100% travel (hysteresis) + 6. Throttle resistance - default rigid ramp 0..max_force """ def __init__(self, settings): @@ -254,11 +292,16 @@ def R2(self, t, s, now): if spin is not None: return spin - # 5. Firmware end wall - hard wall near 100% travel (latched via hysteresis) + # 5. Turbo lag - boost climbing, brief deep rumble + turbo = self.anim.turbo_lag(t, s, now) + if turbo is not None: + return turbo + + # 6. Firmware end wall - hard wall near 100% travel (latched via hysteresis) self._r2_in_wall = _wall_state(accel, self._r2_in_wall, s.throttle_wall_engage_at, s.throttle_wall_release_at) if self._r2_in_wall: return self.wall - # 6. Throttle resistance - default rigid ramp + # 7. Throttle resistance - default rigid ramp return self.anim.throttle_ramp(t, s) diff --git a/src/modules/gui/settings_tab.py b/src/modules/gui/settings_tab.py index af599f1..2303d59 100644 --- a/src/modules/gui/settings_tab.py +++ b/src/modules/gui/settings_tab.py @@ -31,11 +31,29 @@ ("brake_static_wall_at", "Wall position on the trigger", 0, 255, ""), ("brake_static_wall_force", "Wall hardness", 0, 255, ""), ]), + ("Surface brake resistance", [ + ("enable_surface_brake", "Surface brake resistance", None, None, + "Softer brake on loose surfaces, firmer on tarmac."), + ("surface_brake_tarmac", "Tarmac multiplier", 0.0, 2.0, + "1.0 = unchanged. Scales final resistance force."), + ("surface_brake_dirt", "Dirt multiplier", 0.0, 2.0, + "Lower = softer brake on dirt. Default 0.5."), + ("surface_brake_gravel", "Gravel multiplier", 0.0, 2.0, + "Lower = softer brake on gravel/water. Default 0.25."), + ]), ("Right trigger - Gas force", [ ("throttle_baseline_force", "Resting stiffness", 0, 255, ""), ("throttle_max_force", "Hard-press stiffness", 0, 255, ""), ("throttle_curve", "Stiffness curve shape", 0.1, 20.0, ""), ]), + ("Speed-based throttle", [ + ("enable_speed_throttle", "Speed-based throttle", None, None, + "Extra resistance at low speed for precise control, lighter at high speed."), + ("speed_throttle_boost", "Extra force at standstill", 0, 255, + "0 = off. Flat resistance added at 0 km/h, fading to normal."), + ("speed_throttle_fade_km", "Fade-out speed (km/h)", 0.0, 500.0, + "Speed where boost fully fades. Default 80."), + ]), ("ABS (anti-lock brake) rumble", [ ("abs_brake_threshold", "Only when braking harder than", 0, 255, ""), ("abs_min_speed_kmh", "Only when faster than (km/h)", 0.0, 500.0, ""), @@ -56,6 +74,18 @@ ("Idle buzz", [ ("idle_amp_high", "Idle strength", 0, 255, ""), ]), + ("Turbo lag", [ + ("enable_turbo_lag", "Turbo lag vibration", None, None, + "Brief R2 rumble when boost pressure climbs."), + ("turbo_lag_freq", "Rumble speed (Hz)", 0, 255, + "Deep rumble frequency. Default 8."), + ("turbo_lag_amp", "Rumble strength", 0, 255, + "Max vibration amplitude. Default 40."), + ("turbo_lag_threshold", "Boost change sensitivity", 0.0, 1.0, + "Min boost delta per tick to trigger. Default 0.15."), + ("turbo_lag_cooldown_ms", "Cooldown (ms)", 0.0, 2000.0, + "Min gap between rumbles. Suppresses steady-state jitter. Default 300."), + ]), ("Gear shift thump", [ ("gear_shift_freq", "Thump speed (Hz)", 0, 255, ""), ("gear_shift_amp", "Thump strength", 0, 255, ""), diff --git a/src/modules/tui/settings_tab.py b/src/modules/tui/settings_tab.py index 682aa9e..1a2b198 100644 --- a/src/modules/tui/settings_tab.py +++ b/src/modules/tui/settings_tab.py @@ -28,11 +28,29 @@ ("brake_static_wall_at", "Wall position on the trigger", 0, 255, ""), ("brake_static_wall_force", "Wall hardness", 0, 255, ""), ]), + ("Surface brake resistance", [ + ("enable_surface_brake", "Surface brake resistance", None, None, + "Softer brake on loose surfaces, firmer on tarmac."), + ("surface_brake_tarmac", "Tarmac multiplier", 0.0, 2.0, + "1.0 = unchanged. Scales final resistance force."), + ("surface_brake_dirt", "Dirt multiplier", 0.0, 2.0, + "Lower = softer brake on dirt. Default 0.5."), + ("surface_brake_gravel", "Gravel multiplier", 0.0, 2.0, + "Lower = softer brake on gravel/water. Default 0.25."), + ]), ("Right trigger - Gas force", [ ("throttle_baseline_force", "Resting stiffness", 0, 255, ""), ("throttle_max_force", "Hard-press stiffness", 0, 255, ""), ("throttle_curve", "Stiffness curve shape", 0.1, 20.0, ""), ]), + ("Speed-based throttle", [ + ("enable_speed_throttle", "Speed-based throttle", None, None, + "Extra resistance at low speed for precise control, lighter at high speed."), + ("speed_throttle_boost", "Extra force at standstill", 0, 255, + "0 = off. Flat resistance added at 0 km/h, fading to normal."), + ("speed_throttle_fade_km", "Fade-out speed (km/h)", 0.0, 500.0, + "Speed where boost fully fades. Default 80."), + ]), ("ABS (anti-lock brake) rumble", [ ("abs_brake_threshold", "Only when braking harder than", 0, 255, ""), ("abs_min_speed_kmh", "Only when faster than (km/h)", 0.0, 500.0, ""), @@ -53,6 +71,18 @@ ("Idle buzz", [ ("idle_amp_high", "Idle strength", 0, 255, ""), ]), + ("Turbo lag", [ + ("enable_turbo_lag", "Turbo lag vibration", None, None, + "Brief R2 rumble when boost pressure climbs."), + ("turbo_lag_freq", "Rumble speed (Hz)", 0, 255, + "Deep rumble frequency. Default 8."), + ("turbo_lag_amp", "Rumble strength", 0, 255, + "Max vibration amplitude. Default 40."), + ("turbo_lag_threshold", "Boost change sensitivity", 0.0, 1.0, + "Min boost delta per tick to trigger. Default 0.15."), + ("turbo_lag_cooldown_ms", "Cooldown (ms)", 0.0, 2000.0, + "Min gap between rumbles. Suppresses steady-state jitter. Default 300."), + ]), ("Gear shift thump", [ ("gear_shift_freq", "Thump speed (Hz)", 0, 255, ""), ("gear_shift_amp", "Thump strength", 0, 255, ""),