Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

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

Aperta
#216 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
50/100
Tipo di issue
Funzionalità
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
lua
Ambito
game-dev

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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")
+    )
Lingua principale
Lua
Stelle
282
Fork
17
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di id577/FS25_AdvancedDamageSystem

Tutte le issue di id577/FS25_AdvancedDamageSystem

Issue simili

Altre issue su Lua

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.