Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

[WIP] Realistic Viscous Radiator Fan Clutch Dynamics, Dynamic RPM Audio & Dual-Mode Failure Simulation

Đang mở
#216 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
50/100
Loại issue
Tính năng
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
lua
Lĩnh vực
game-dev

Hướng nghiên cứu

The implementation touches four Lua files: scripts/core/ADS_Breakdowns.lua for failure mode determination, scripts/core/ADS_Thermal.lua for viscous fan engagement calculation, scripts/core/ADS_Specialization.lua for sound updates and savegame persistence, and gui/ADS_SettingsPage.lua for the new settings options. Start by reading the existing fanClutchHealth handling and fanNoice sample usage. 'Done' means the new fan clutch dynamics, dual-mode failures, dynamic audio, and localization strings are merged and tested.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

enhancement

Status: Implemented on top of current ADS codebase; currently in active in-game testing.
Hi @id577 and @Squallqt!
ADS already has a great foundation with spec.fanClutchHealth, cooling interaction, and the fanNoice.ogg sample.
I decided to expand upon your existing implementation to bring even more realism to the fan clutch in three key areas:

1. Dynamic Rev-Matched Fan Roar (Thermal Engagement)

Currently, fanNoice is mainly triggered as a static-pitch defect sound.

  • In this update, as engine coolant temperature climbs under heavy field draft (between 85°C and 94°C), the fan clutch engages progressively with hydraulic slew-rate smoothing.
  • While engaged, updateViscousFanSound(dt) dynamically modulates both the volume and pitch of fanNoice.ogg with actual engine RPM (getLastModulatedMotorRpm()).
  • You get that iconic heavy-diesel fan roar when pulling hard on the field, which winds down naturally as the engine cools or idles.
  • Added a volume slider in the Thermal Settings page (Off, 10% to 100%, default 80%).
2. Dual-Mode Failure Realism (Configurable in Settings Menu)

Instead of a single generic defect behavior, when FAN_CLUTCH_FAILURE triggers, it rolls one of the two real-world failure modes:

  • Mode 2 — Slipping ("Solta / Desengatada"): The clutch loses silicone fluid or the bimetal spring fatigues. It fails to lock up under heat, stays silent, and causes the engine to overheat rapidly during low-speed field work (brokenFanModifier drops to 0.30).
  • Mode 1 — Seized / Locked Solid ("Agarrada / Bloqueada"): Mechanical bearing seizure. The fan is permanently locked 1:1 with engine RPM, roaring aggressively all the time (even cold), providing continuous forced airflow, with a slight parasitic load (+3%).
  • Mode 0: Normal operation (cleared upon repair).
  • Player-Configurable Probability: Added an option under Thermal Settings (Fan Clutch Breakdown Behavior) with default 70% / 30% (adjustable from 100/0 to 0/100), synced across multiplayer and persisted to settings.xml.
3. Localization & ADS Architecture Integration
  • Full Localization: Added translation strings for all new options across all 15 languages supported by ADS (en, pt, br, de, fr, es, it, pl, ru, uk, cz, hu, tr, fi, kr).
  • HUD Debug: Displays Fan: % | Clutch: % | Mode [NORM/LOCKED/SLIP] | Vol: % under the Engine Temp debug block.
  • Console Command: Integrated into ads_getDebugVehicleInfo.
  • Persistence: #fanClutchFailureMode and #viscousFanEngagement are registered in schemaSavegame and saved/loaded seamlessly.

View Implementation Diffs (Click to expand)
scripts/core/ADS_Breakdowns.lua
--- a/scripts/core/ADS_Breakdowns.lua
+++ b/scripts/core/ADS_Breakdowns.lua
@@ -4617,11 +4617,23 @@
 ADS_Breakdowns.EffectApplicators.FAN_CLUTCH_MODIFIER = {
     apply = function(vehicle, effectData, handler)
         log_dbg("Applying FAN_CLUTCH_MODIFIER:", effectData.value)
         local spec = vehicle.spec_AdvancedDamageSystem
         spec.fanClutchHealth = math.max(1.0 + effectData.value, 0.1)
+
+        -- Determine dual-mode failure (0 = normal, 1 = locked, 2 = slipping)
+        if spec.fanClutchFailureMode == nil or spec.fanClutchFailureMode == 0 then
+            local lockedChance = (ADS_Config.THERMAL and ADS_Config.THERMAL.VISCOUS_FAN_MODE_LOCKED_CHANCE) or 0.30
+            spec.fanClutchFailureMode = (math.random() <= lockedChance) and 1 or 2
+            log_dbg(string.format("FAN_CLUTCH_MODIFIER mode rolled: %d (%s)",
+                spec.fanClutchFailureMode,
+                spec.fanClutchFailureMode == 1 and "LOCKED" or "SLIPPING"
+            ))
+        end
     end,
 
     remove = function(vehicle, handler)
         log_dbg("Removing FAN_CLUTCH_MODIFIER effect.")
         local spec = vehicle.spec_AdvancedDamageSystem
         spec.fanClutchHealth = 1.0
+        spec.fanClutchFailureMode = 0
     end
 }
scripts/core/ADS_Thermal.lua
--- a/scripts/core/ADS_Thermal.lua
+++ b/scripts/core/ADS_Thermal.lua
@@ -216,6 +216,36 @@
     end
 
+    -- --- Viscous / Electro-Viscous Fan Clutch Simulation ---
+    if isMotorStarted then
+        local targetEngagement = 0.0
+
+        if spec.fanClutchFailureMode == 1 then
+            targetEngagement = 1.0 -- Locked solid
+        elseif spec.fanClutchFailureMode == 2 then
+            targetEngagement = 0.03 * sanitizeNumber(spec.fanClutchHealth, 1.0, 0, 1) -- Slipping
+        else
+            local engageStartTemp = (C.VISCOUS_FAN_ENGAGE_START_TEMP or 85.0)
+            local engageFullTemp = (C.VISCOUS_FAN_ENGAGE_FULL_TEMP or 94.0)
+            targetEngagement = math.clamp((rawEngineTemp - engageStartTemp) / (engageFullTemp - engageStartTemp), 0.0, 1.0)
+
+            if spec.thermostatState and spec.thermostatState > 0.80 then
+                targetEngagement = math.max(targetEngagement, (spec.thermostatState - 0.80) * 2.5)
+            end
+
+            local fanClutchHealth = sanitizeNumber(spec.fanClutchHealth, 1.0, 0, 1)
+            targetEngagement = targetEngagement * fanClutchHealth
+        end
+
+        -- Viscous fluid has hydraulic inertia (~2.5s to fully engage or disengage)
+        local slewRate = (safeDt > 0 and (safeDt / 2500)) or 0.05
+        spec.viscousFanEngagement = (spec.viscousFanEngagement or 0.0) + (targetEngagement - (spec.viscousFanEngagement or 0.0)) * math.clamp(slewRate, 0.0, 1.0)
+    else
+        spec.viscousFanEngagement = 0.0
+    end
+
     dbg.totalHeat = heat
     dbg.totalCooling = cooling
     dbg.radiatorCooling = radiatorCooling
scripts/core/ADS_Specialization.lua
--- a/scripts/core/ADS_Specialization.lua
+++ b/scripts/core/ADS_Specialization.lua
@@ -1041,2 +1041,4 @@
     schemaSavegame:register(XMLValueType.FLOAT,  baseKey .. "#transmissionThermostatState", "Transmission Thermostat Position")
+    schemaSavegame:register(XMLValueType.INT,    baseKey .. "#fanClutchFailureMode", "Fan Clutch Failure Mode (0=Normal, 1=Locked, 2=Slipping)")
+    schemaSavegame:register(XMLValueType.FLOAT,  baseKey .. "#viscousFanEngagement", "Viscous Fan Clutch Engagement")
@@ -1193,4 +1195,5 @@
     SpecializationUtil.registerFunction(vehicleType, "updateTransmissionThermalModel", ADS_Thermal.updateTransmissionThermalModel)
     SpecializationUtil.registerFunction(vehicleType, "getSmoothedTemperature", ADS_Thermal.getSmoothedTemperature)
+    SpecializationUtil.registerFunction(vehicleType, "updateViscousFanSound", AdvancedDamageSystem.updateViscousFanSound)
@@ -1645,2 +1648,4 @@
         xmlFile:setValue(key .. "#transmissionThermostatState", AdvancedDamageSystem.sanitizeNumber(spec.transmissionThermostatState, 0.0, 0.0, 1.0))
+        xmlFile:setValue(key .. "#fanClutchFailureMode", tonumber(spec.fanClutchFailureMode) or 0)
+        xmlFile:setValue(key .. "#viscousFanEngagement", AdvancedDamageSystem.sanitizeNumber(spec.viscousFanEngagement, 0.0, 0.0, 1.0))
@@ -1854,2 +1859,4 @@
     self.spec_AdvancedDamageSystem.fanClutchHealth = 1.0
+    self.spec_AdvancedDamageSystem.fanClutchFailureMode = 0
+    self.spec_AdvancedDamageSystem.viscousFanEngagement = 0.0
@@ -4523,4 +4534,63 @@
+function AdvancedDamageSystem:updateViscousFanSound(dt)
+    local spec = self.spec_AdvancedDamageSystem
+    if spec == nil or spec.samples == nil then return end
+    local sample = spec.samples.fanNoice
+    if sample == nil then return end
+
+    if spec.fanClutchFailureMode == 2 then
+        if g_soundManager:getIsSamplePlaying(sample) then
+            g_soundManager:stopSample(sample, 0, 0)
+        end
+        return
+    end
+
+    local motor = self:getMotor()
+    if motor == nil or not self:getIsMotorStarted() then
+        if g_soundManager:getIsSamplePlaying(sample) then
+            g_soundManager:stopSample(sample, 0, 0)
+        end
+        return
+    end
+
+    local configVolume = (ADS_Config.THERMAL and ADS_Config.THERMAL.VISCOUS_FAN_VOLUME) or 0.80
+    local engagement = spec.viscousFanEngagement or 0.0
+
+    if configVolume <= 0.01 or engagement <= 0.02 then
+        if g_soundManager:getIsSamplePlaying(sample) then
+            g_soundManager:stopSample(sample, 0, 0)
+        end
+        return
+    end
+
+    local minRpm = tonumber(motor.minRpm) or 800
+    local maxRpm = math.max(tonumber(motor.maxRpm) or 2200, minRpm + 1)
+    local curRpm = (motor.getLastModulatedMotorRpm ~= nil and tonumber(motor:getLastModulatedMotorRpm())) or tonumber(motor.lastMotorRpm) or minRpm
+    local rpmN = math.clamp((curRpm - minRpm) / (maxRpm - minRpm), 0, 1)
+
+    local baseVolume = configVolume * engagement * (0.25 + 0.75 * (rpmN ^ 1.15))
+    local pitchOffset = (0.35 * (rpmN ^ 1.20)) * engagement
+
+    sample.loops = 0
+    sample.volumeScale = (sample.adsOriginalVolumeScale or sample.volumeScale) * baseVolume
+
+    if not g_soundManager:getIsSamplePlaying(sample) then
+        g_soundManager:playSample(sample)
+    end
+
+    g_soundManager:setSamplePitchOffset(sample, pitchOffset)
+    g_soundManager:setSampleVolumeOffset(sample, 0)
+end
@@ -4590,2 +4660,3 @@
     ADS_ThrottleControl.update(self)
+    self:updateViscousFanSound(dt)
gui/ADS_SettingsPage.lua
--- a/gui/ADS_SettingsPage.lua
+++ b/gui/ADS_SettingsPage.lua
@@ -54,4 +54,6 @@
         maxDirtInfluence = ADS_Config.THERMAL.MAX_DIRT_INFLUENCE,
+        viscousFanVolume = ADS_Config.THERMAL.VISCOUS_FAN_VOLUME,
+        viscousFanModeLockedChance = ADS_Config.THERMAL.VISCOUS_FAN_MODE_LOCKED_CHANCE,
         warmingBoostPower = ADS_Config.THERMAL.WARMING_BOOST_POWER,
@@ -579,4 +581,18 @@
     page.ads_viscousFanVolume = ADS_InGameSettings:addMultiTextOption(
         page,
         "onViscousFanVolumeChanged",
         ADS_InGameSettings.steps.viscousFanVolume.texts,
         g_i18n:getText("ads_viscousFanVolume_label"),
         g_i18n:getText("ads_viscousFanVolume_tooltip")
     )
+    page.ads_viscousFanModeLockedChance = ADS_InGameSettings:addMultiTextOption(
+        page,
+        "onViscousFanModeLockedChanceChanged",
+        ADS_InGameSettings.steps.viscousFanModeLockedChance.texts,
+        g_i18n:getText("ads_viscousFanModeLockedChance_label"),
+        g_i18n:getText("ads_viscousFanModeLockedChance_tooltip")
+    )
Ngôn ngữ chính
Lua
Star
282
Fork
17
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của id577/FS25_AdvancedDamageSystem

Tất cả issue của id577/FS25_AdvancedDamageSystem

Issue tương tự

Thêm issue về Lua

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.