-- Author: U_BMP
-- Group: https://vk.com/biomodprod_utilit_fs
-- Date: 19.11.2025

MudSystemSettings = {}
MudSystemSettings.name = g_currentModName or "MudSystemSettings"
MudSystemSettings.path = g_currentModDirectory or ""

MudSystemSettings.SETTINGS = {}
MudSystemSettings.CONTROLS = {}
MudSystemSettings.CONTROL_BOXES = {}

MudSystemSettingsPage = {}
local MudSystemSettingsPage_mt = Class(MudSystemSettingsPage, FrameElement)

function MudSystemSettingsPage.new(customMt)
    local self = FrameElement.new(nil, customMt or MudSystemSettingsPage_mt)
    self.controls = {}
    return self
end

function MudSystemSettingsPage:onClickMudSystem()
    if MudSystemSettings ~= nil then
        MudSystemSettings:onClickMudSystem()
    end
end

function MudSystemSettings:onClickMudSystem()
    if g_inGameMenu ~= nil and g_inGameMenu.pageSettings ~= nil then
        self:refreshMudSettingsSubCategoryState(g_inGameMenu.pageSettings)
        if self._modSubCategoryState ~= nil then
            g_inGameMenu.pageSettings.subCategoryPaging:setState(self._modSubCategoryState, true)
        end
    end
end

addModEventListener(MudSystemSettings)

MudSystemSettings._mpWantsRequest = false
MudSystemSettings._mpRequestSent  = false
MudSystemSettings._mpSyncReceived = false
MudSystemSettings._mpRequestRetryTimer = 0
MudSystemSettings._applyingPreset = false
MudSystemSettings._applyingSnapshot = false
MudSystemSettings._refreshingMenuStates = false

-- ---------------------------------------------------------
-- CONFIG
-- ---------------------------------------------------------

MudSystemSettings.xmlKey  = "mudSystemPhysicsSettings"
MudSystemSettings.xmlFile = "modSettings/MudSystemPhysics.xml"
MudSystemSettings.savegameXmlFile = "MudSystemPhysicsSettings.xml"
MudSystemSettings.localXmlKey  = "mudSystemPhysicsLocalSettings"
MudSystemSettings.localXmlFile = "modSettings/MudSystemPhysicsHUD.xml"

MudSystemSettings.difficultyPreset = 3
MudSystemSettings.presetXmlKey = "mudSystemPhysicsSettings"
MudSystemSettings.PRESET_FILES = {
    [1] = "presets/MudSystemPhysics_easy.xml",
    [2] = "presets/MudSystemPhysics_balanced.xml"
}
MudSystemSettings.DIFFICULTY_ITEM_IDS = {
    mp_permaStuckEnable = true,
    mp_permaStuckChanceOnStruggle = true,
    mp_sinkInSpeed = true,
    mp_wheelBrakeBase = true,
    mp_wheelBrakeFromSink = true,
    mp_wheelBrakeFromSlip = true,
    fg_permaStuckEnable = true,
    fg_radiusSinkInSpeed = true,
    fg_slipMinMul = true,
    fg_slipMaxMul = true,
    fg_wheelBrakeBase = true,
    fg_wheelBrakeFromSink = true,
    fg_wheelBrakeFromSlip = true
}
MudSystemSettings.DIFFICULTY_PROFILE_FIELDS = {
    mud = true,
    wetMul = true,
    sinkMul = true,
    brakeMul = true,
    motorMul = true,
    radiusMinFactor = true,
    slip = true,
    permaStuck = true
}

MudSystemSettings.hudMoveStep = 5
MudSystemSettings.hudPosPreset = 1
MudSystemSettings.hudResetPos = false

-- Настройки каждого слоя земли
-- false = полностью скрыты из меню, но продолжают работать и грузиться из XML
MudSystemSettings.showFieldGroundProfiles = false
MudSystemSettings.showTirePressureAdvanced = false
MudSystemSettings.showSoilDryingAdvanced = false
MudSystemSettings.showMudAdvanced = false
MudSystemSettings.showFieldAdvanced = false

local function clamp(x, mn, mx)
    if x == nil then return mn end
    if x < mn then return mn end
    if x > mx then return mx end
    return x
end

local function boolTo01(b) return b and 1 or 0 end
local function int01ToBool(v) return (tonumber(v) or 0) >= 0.5 end

local function fmtFloat(v, decimals)
    decimals = decimals or 2
    return string.format("%0." .. tostring(decimals) .. "f", v)
end

function MudSystemSettings:buildRange(id, minV, maxV, step, decimals, displayScale, displayDecimals)
    local setting = self.SETTINGS[id]
    if setting ~= nil and setting.values ~= nil and setting.strings ~= nil then
        return setting.values, setting.strings
    end

    displayScale = tonumber(displayScale) or 1
    displayDecimals = displayDecimals or decimals
    local values, strings = {}, {}
    local n = math.floor((maxV - minV) / step + 0.5)
    for i = 0, n do
        local v = minV + i * step
        v = clamp(v, minV, maxV)
        values[#values + 1] = v
        strings[#strings + 1] = fmtFloat(v * displayScale, displayDecimals)
    end

    self.SETTINGS[id] = self.SETTINGS[id] or {}
    self.SETTINGS[id].values = values
    self.SETTINGS[id].strings = strings
    return values, strings
end

function MudSystemSettings:getTextSafe(key, fallback)
    if g_i18n ~= nil and g_i18n:hasText(key) then
        return g_i18n:getText(key)
    end
    return fallback or key
end

-----------------------------------------------------------
-- ITEMS
-----------------------------------------------------------
MudSystemSettings.items = {
    -- Difficulty presets
    { id="mudsys_difficultyPreset", section="preset", type="float", target="MudSystemSettings", key="difficultyPreset", min=1, max=3, step=1, decimals=0 },

    -- General
    { id="fg_eraseFruitEnable",         section="general", type="bool",  target="FieldGroundMudPhysics", key="eraseFruitEnable" },
    { id="fg_eraseFoliageOnlyFields",   section="general", type="bool",  target="FieldGroundMudPhysics", key="eraseFoliageOnlyFields" },
    { id="tp_enabled",                   section="tirePressure", type="bool",  target="TirePressureSystem", key="enabled" },
    { id="tp_showAdvancedSettings",      section="tirePressure", type="bool",  target="MudSystemSettings", key="showTirePressureAdvanced", localOnly=true },
    { id="tp_lowPressureMudGrip",        section="tirePressure", type="float", target="TirePressureSystem", key="lowPressureMudGrip", min=1.0, max=2.5, step=0.01, decimals=2, advanced=true },
    { id="tp_highPressureMudGrip",       section="tirePressure", type="float", target="TirePressureSystem", key="highPressureMudGrip", min=0.5, max=1.2, step=0.01, decimals=2, advanced=true },
    { id="tp_lowPressureSinkMul",        section="tirePressure", type="float", target="TirePressureSystem", key="lowPressureSinkMul", min=0.25, max=1.0, step=0.01, decimals=2, advanced=true },
    { id="tp_highPressureSinkMul",       section="tirePressure", type="float", target="TirePressureSystem", key="highPressureSinkMul", min=1.0, max=1.8, step=0.01, decimals=2, advanced=true },
    { id="tp_lowPressureMudBrakeMul",    section="tirePressure", type="float", target="TirePressureSystem", key="lowPressureMudBrakeMul", min=0.25, max=1.0, step=0.01, decimals=2, advanced=true },
    { id="tp_highPressureMudBrakeMul",   section="tirePressure", type="float", target="TirePressureSystem", key="highPressureMudBrakeMul", min=1.0, max=1.8, step=0.01, decimals=2, advanced=true },
    { id="tp_lowPressureRadiusSinkMul",  section="tirePressure", type="float", target="TirePressureSystem", key="lowPressureRadiusSinkMul", min=0.10, max=1.0, step=0.01, decimals=2, advanced=true },
    { id="tp_highPressureRadiusSinkMul", section="tirePressure", type="float", target="TirePressureSystem", key="highPressureRadiusSinkMul", min=1.0, max=2.0, step=0.01, decimals=2, advanced=true },
    { id="tp_lowPressureSinkOutMul",     section="tirePressure", type="float", target="TirePressureSystem", key="lowPressureSinkOutMul", min=1.0, max=2.0, step=0.01, decimals=2, advanced=true },
    { id="tp_highPressureSinkOutMul",    section="tirePressure", type="float", target="TirePressureSystem", key="highPressureSinkOutMul", min=0.5, max=1.0, step=0.01, decimals=2, advanced=true },
    { id="tp_lowPressureMudSpeedLimitMul", section="tirePressure", type="float", target="TirePressureSystem", key="lowPressureMudSpeedLimitMul", min=1.0, max=2.0, step=0.01, decimals=2, advanced=true },
    { id="tp_highPressureMudSpeedLimitMul", section="tirePressure", type="float", target="TirePressureSystem", key="highPressureMudSpeedLimitMul", min=0.5, max=1.0, step=0.01, decimals=2, advanced=true },
    { id="tp_lowPressureMotorLoadMul",   section="tirePressure", type="float", target="TirePressureSystem", key="lowPressureMotorLoadMul", min=0.25, max=1.0, step=0.01, decimals=2, advanced=true },
    { id="tp_highPressureMotorLoadMul",  section="tirePressure", type="float", target="TirePressureSystem", key="highPressureMotorLoadMul", min=1.0, max=1.8, step=0.01, decimals=2, advanced=true },

    -- Field wheel puncture and repair workflow
    { id="puncture_enabled", section="puncture", type="bool", target="PunctureSystem", key="enabled" },
    { id="puncture_roadGarbageEnabled", section="puncture", type="bool", target="RoadGarbageSystem", key="enabled" },
    { id="puncture_roadGarbagePointCount", section="puncture", type="float", target="RoadGarbageSystem", key="pointCount", min=10, max=150, step=10, decimals=0 },
    { id="puncture_chancePerKm", section="puncture", type="float", target="PunctureSystem", key="chancePerKm", min=0.01, max=0.50, step=0.01, decimals=2, displayScale=100, displayDecimals=0 },
    { id="puncture_chanceDistanceKm", section="puncture", type="float", target="PunctureSystem", key="chanceDistanceKm", min=1, max=100, step=1, decimals=0 },
    { id="puncture_requireJack", section="puncture", type="bool", target="PunctureSystem", key="requireJack" },
    { id="puncture_showCarriedWheel", section="puncture", type="bool", target="PunctureSystem", key="showCarriedWheel" },
    { id="puncture_highlightWheel", section="puncture", type="bool", target="PunctureSystem", key="highlightPuncturedWheel" },
    { id="puncture_aiPunctures", section="puncture", type="bool", target="PunctureSystem", key="aiPuncturesEnabled" },
    { id="puncture_aiAutoRepair", section="puncture", type="bool", target="PunctureSystem", key="aiAutoRepairEnabled" },

    -- Local field wetness + field info HUD
    { id="flw_enabled",                  section="localWetness", type="bool",  target="FieldLocalWetness", key="enabled" },
	
	-- HUD: icon preset
    { id="hud_iconPreset", 				section="hud", type="float", target="MudWetnessMiniHUD", key="iconPreset", min=1, max=3, step=1, decimals=0 },
	
	-- Wet Taimer
	{ id="hud_showNextPrecipTimer", section="hud", type="bool", target="MudWetnessMiniHUD", key="showNextPrecipTimer" },
	{ id="hud_showCurrentPrecipRemainingTimer", section="hud", type="bool", target="MudWetnessMiniHUD", key="showCurrentPrecipRemainingTimer" },
	{ id="hud_permaStuckNotificationsEnable", section="hud", type="bool", target="MudPhysics", key="permaStuckNotificationsEnable" },

	-- HUD: block position (pixel offsets)
    { id="hud_offsetXPx", section="hud", type="float", target="MudWetnessMiniHUD", key="offsetXPx", min=-2600, max=2600, step=1, decimals=0 },
    { id="hud_offsetYPx", section="hud", type="float", target="MudWetnessMiniHUD", key="offsetYPx", min=-2400, max=20, step=1, decimals=0 },
    { id="tpHud_offsetXPx", section="hud", type="float", target="TirePressureHUD", key="offsetXPx", min=-2600, max=2600, step=1, decimals=0 },
    { id="tpHud_offsetYPx", section="hud", type="float", target="TirePressureHUD", key="offsetYPx", min=-2400, max=2400, step=1, decimals=0 },
    { id="hud_moveStep", section="hud", type="float", target="MudSystemSettings", key="hudMoveStep" },
    { id="hud_resetPos", section="hud", type="bool", target="MudSystemSettings", key="hudResetPos" },
    { id="hud_posPreset", section="hud", type="float", target="MudSystemSettings", key="hudPosPreset" },

    -- Soil drying / wetness tuning
    { id="sdt_enabled",                   section="drying", type="bool",  target="BMP_SoilDryingTuner", key="enabled" },
    { id="sdt_showAdvancedSettings",      section="drying", type="bool",  target="MudSystemSettings", key="showSoilDryingAdvanced", localOnly=true },
    { id="sdt_dryDurationMul",            section="drying", type="float", target="BMP_SoilDryingTuner", key="dryDurationMul",           min=1.00, max=20.0, step=1.0, decimals=0, advanced=true },
    { id="sdt_wetDurationMul",            section="drying", type="float", target="BMP_SoilDryingTuner", key="wetDurationMul",           min=0.50, max=20.0, step=0.50, decimals=2, advanced=true },
    { id="sdt_enableDynamic",             section="drying", type="bool",  target="BMP_SoilDryingTuner", key="enableDynamic", advanced=true },
    { id="sdt_coldTemp",                  section="drying", type="float", target="BMP_SoilDryingTuner", key="coldTemp",                 min=-20.0, max=15.0, step=1.0, decimals=0, advanced=true },
    { id="sdt_warmTemp",                  section="drying", type="float", target="BMP_SoilDryingTuner", key="warmTemp",                 min=0.0,   max=40.0, step=1.0, decimals=0, advanced=true },
    { id="sdt_coldDryExtraMul",           section="drying", type="float", target="BMP_SoilDryingTuner", key="coldDryExtraMul",          min=1.0,   max=10.0, step=0.10, decimals=2, advanced=true },
    { id="sdt_cloudyFrom",                section="drying", type="float", target="BMP_SoilDryingTuner", key="cloudyFrom",               min=0.0,   max=1.0,  step=0.05, decimals=2, advanced=true },
    { id="sdt_cloudyTo",                  section="drying", type="float", target="BMP_SoilDryingTuner", key="cloudyTo",                 min=0.0,   max=1.0,  step=0.05, decimals=2, advanced=true },
    { id="sdt_cloudDryExtraMul",          section="drying", type="float", target="BMP_SoilDryingTuner", key="cloudDryExtraMul",         min=1.0,   max=10.0, step=0.10, decimals=2, advanced=true },
    { id="sdt_maxTotalExtraMul",          section="drying", type="float", target="BMP_SoilDryingTuner", key="maxTotalExtraMul",         min=1.0,   max=20.0, step=0.10, decimals=2, advanced=true },
    { id="sdt_dynamicWetnessThreshold",   section="drying", type="float", target="BMP_SoilDryingTuner", key="dynamicWetnessThreshold",  min=0.0,   max=1.0,  step=0.01, decimals=2, advanced=true },

    -- { id="mudsys_resetAll",             section="mud", type="bool",  target="MudSystemSettings", key="resetAll" },
	
    -- MudPhysics
    { id="mp_enabled",                  section="mud", type="bool",  target="MudPhysics", key="enabled" },
    { id="mp_showAdvancedSettings",     section="mud", type="bool",  target="MudSystemSettings", key="showMudAdvanced", localOnly=true },
	
	-- terrain brush: DIRT
    { id="mp_useDIRTBrushes",          section="mud", type="bool",  target="MudPhysics", key="useDIRTBrushes" },
    { id="mp_freezeMudByTempEnable",    section="mud", type="bool",  target="MudPhysics", key="freezeMudByTempEnable", advanced=true },
    { id="mp_freezeAllLayersInWinterEnable", section="mud", type="bool",  target="MudPhysics", key="freezeAllLayersInWinterEnable", advanced=true },
    { id="mp_freezeAllLayersTempC",        section="mud", type="float", target="MudPhysics", key="freezeAllLayersTempC", min=-15.0, max=1.0, step=1, decimals=1, advanced=true },
    { id="mp_extraWheelSinkEnable",     section="mud", type="bool",  target="MudPhysics", key="extraWheelSinkEnable", advanced=true },
    { id="mp_winterSlipEnable",         section="mud", type="bool",  target="MudPhysics", key="winterSlipEnable", advanced=true },
    { id="mp_winterSlipTempC",          section="mud", type="float", target="MudPhysics", key="winterSlipTempC",        min=-10.0, max=0.0,  step=1.0,  decimals=0, advanced=true },
    { id="mp_winterSlipMul",            section="mud", type="float", target="MudPhysics", key="winterSlipMul",          min=0.0,   max=2.0,  step=0.01, decimals=2, advanced=true },
    { id="mp_normalSlipEnable",         section="mud", type="bool",  target="MudPhysics", key="normalSlipEnable", advanced=true },
    { id="mp_normalSlipMul",            section="mud", type="float", target="MudPhysics", key="normalSlipMul",          min=0.0,   max=1.0,  step=0.01, decimals=2, advanced=true },
    { id="mp_rainSlipEnable",           section="mud", type="bool",  target="MudPhysics", key="rainSlipEnable", advanced=true },
    { id="mp_rainSlipWetnessMin",       section="mud", type="float", target="MudPhysics", key="rainSlipWetnessMin",     min=0.0,   max=1.0,  step=0.01, decimals=2, advanced=true },
    { id="mp_rainSlipMul",              section="mud", type="float", target="MudPhysics", key="rainSlipMul",            min=0.0,   max=1.0,  step=0.01, decimals=2, advanced=true },
    { id="mp_rainSlipMaxMul",           section="mud", type="float", target="MudPhysics", key="rainSlipMaxMul",         min=0.0,   max=1.0,  step=0.01, decimals=2, advanced=true },
    { id="mp_permaStuckEnable",         section="mud", type="bool",  target="MudPhysics", key="permaStuckEnable" },
    { id="mp_permaStuckChanceOnStruggle", section="mud", type="float", target="MudPhysics", key="permaStuckChanceOnStruggle", min=0.0, max=0.2, step=0.001, decimals=3, advanced=true },
    { id="mp_motorLoadEnable",          section="mud", type="bool",  target="MudPhysics", key="motorLoadEnable", advanced=true },
    { id="mp_particlesEnable",          section="mud", type="bool",  target="MudPhysics", key="particlesEnable" },
    { id="mp_extraParticlesEnable",     section="mud", type="bool",  target="MudPhysics", key="extraParticlesEnable" },
    { id="mp_extraParticlesPreset",     section="mud", type="float", target="MudPhysics", key="extraParticlesPreset", min=1, max=5, step=1, decimals=0 },
    { id="mp_mudVarStrength",           section="mud", type="float", target="MudPhysics", key="mudVarStrength",         min=0.0,   max=1.0,  step=0.01, decimals=2, advanced=true },
    { id="mp_mudVarCell",               section="mud", type="float", target="MudPhysics", key="mudVarCell",             min=0.0,   max=10.0, step=0.01, decimals=2, menuHidden=true },
    { id="mp_mudBobAmp",                section="mud", type="float", target="MudPhysics", key="mudBobAmp",             min=0.0,   max=1.0,  step=0.01, decimals=2, advanced=true },
    { id="mp_extraParticleOnlyWetMud",  section="mud", type="bool",  target="MudPhysics", key="extraParticleOnlyWetMud", advanced=true },
    { id="mp_extraParticleOffsetY",     section="mud", type="float", target="MudPhysics", key="extraParticleOffsetY",  min=-1.0,  max=1.0,  step=0.01, decimals=2, advanced=true },
    { id="mp_dirtEnable",               section="mud", type="bool",  target="MudPhysics", key="dirtEnable" },
    { id="mp_dirtMinEffMud",            section="mud", type="float", target="MudPhysics", key="dirtMinEffMud",          min=0.0,   max=1.0,  step=0.01,  decimals=2, advanced=true },
    { id="mp_dirtWetnessMin",           section="mud", type="float", target="MudPhysics", key="dirtWetnessMin",         min=0.0,   max=1.0,  step=0.01,  decimals=2, advanced=true },
    { id="mp_dirtBodyPerSec",           section="mud", type="float", target="MudPhysics", key="dirtBodyPerSec",         min=0.0,   max=1.0,  step=0.001, decimals=3, advanced=true },
    { id="mp_wheelBrakeEnable",         section="mud", type="bool",  target="MudPhysics", key="wheelBrakeEnable", advanced=true },
    { id="mp_sinkInSpeed",              section="mud", type="float", target="MudPhysics", key="sinkInSpeed",            min=0.0, max=10.0, step=0.01, decimals=2, advanced=true },
    { id="mp_sinkOutSpeed",             section="mud", type="float", target="MudPhysics", key="sinkOutSpeed",           min=0.0, max=10.0, step=0.01, decimals=2, advanced=true },
    
    { id="mp_radiusMinFactor",          section="mud", type="float", target="MudPhysics", key="radiusMinFactor",        min=0.0, max=1.0,  step=0.01, decimals=2, menuHidden=true },
    { id="mp_emitMultWetMud",           section="mud", type="float", target="MudPhysics", key="emitMultWetMud",         min=0.0, max=50.0, step=0.01, decimals=2, menuHidden=true },
    { id="mp_sizeMultWetMud",           section="mud", type="float", target="MudPhysics", key="sizeMultWetMud",         min=0.0, max=50.0, step=0.01, decimals=2, menuHidden=true },
    { id="mp_speedMultWetMud",          section="mud", type="float", target="MudPhysics", key="speedMultWetMud",        min=0.0, max=50.0, step=0.01, decimals=2, menuHidden=true },
    { id="mp_wheelBrakeBase",           section="mud", type="float", target="MudPhysics", key="wheelBrakeBase",         min=0.0, max=15.0, step=0.01, decimals=2, menuHidden=true },
    { id="mp_wheelBrakeFromSink",       section="mud", type="float", target="MudPhysics", key="wheelBrakeFromSink",     min=0.0, max=15.0, step=0.01, decimals=2, menuHidden=true },
    { id="mp_wheelBrakeFromSlip",       section="mud", type="float", target="MudPhysics", key="wheelBrakeFromSlip",     min=0.0, max=15.0, step=0.01, decimals=2, menuHidden=true },
	-- бонус от ширины
	{ id="mp_widthBonusEnable",         section="mud", type="bool",  target="MudPhysics", key="widthBonusEnable", advanced=true },
    { id="mp_widthBonusRefWidth",       section="mud", type="float", target="MudPhysics", key="widthBonusRefWidth",     min=0.10, max=2.50, step=0.01, decimals=2, advanced=true },
    { id="mp_widthBonusStrength",       section="mud", type="float", target="MudPhysics", key="widthBonusStrength",     min=0.0, max=5.0,  step=0.01, decimals=2, advanced=true },
    { id="mp_widthBonusMax",            section="mud", type="float", target="MudPhysics", key="widthBonusMax",          min=0.0, max=0.95, step=0.01, decimals=2, advanced=true },

    -- FieldGroundMudPhysics
    { id="fg_enabled",                  section="field", type="bool",  target="FieldGroundMudPhysics", key="enabled" },
    { id="fg_showAdvancedSettings",     section="field", type="bool",  target="MudSystemSettings", key="showFieldAdvanced", localOnly=true },
    { id="fg_showFieldGroundProfiles",  section="field", type="bool",  target="MudSystemSettings", key="showFieldGroundProfiles", localOnly=true },
    { id="fg_permaStuckEnable",         section="field", type="bool",  target="FieldGroundMudPhysics", key="permaStuckEnable" },
	{ id="fg_freezeAllLayersInWinterEnable", section="field", type="bool",  target="FieldGroundMudPhysics", key="freezeAllLayersInWinterEnable", advanced=true },
    { id="fg_freezeAllLayersTempC",          section="field", type="float", target="FieldGroundMudPhysics", key="freezeAllLayersTempC", min=-15.0, max=1.0, step=1, decimals=1, advanced=true },
    { id="fg_motorLoadEnable",          section="field", type="bool",  target="FieldGroundMudPhysics", key="motorLoadEnable", advanced=true },
    { id="fg_wheelBrakeEnable",         section="field", type="bool",  target="FieldGroundMudPhysics", key="wheelBrakeEnable", advanced=true },
    { id="fg_radiusSinkEnable",         section="field", type="bool",  target="FieldGroundMudPhysics", key="radiusSinkEnable", advanced=true },
    
    { id="fg_radiusMinFactor",          section="field", type="float", target="FieldGroundMudPhysics", key="radiusMinFactor",     min=0.0, max=1.0,  step=0.01, decimals=2, menuHidden=true },
    { id="fg_radiusSinkInSpeed",        section="field", type="float", target="FieldGroundMudPhysics", key="radiusSinkInSpeed",   min=0.0, max=2.0, step=0.001, decimals=3, advanced=true },
    { id="fg_radiusSinkOutSpeed",       section="field", type="float", target="FieldGroundMudPhysics", key="radiusSinkOutSpeed",  min=0.0, max=2.0, step=0.001, decimals=3, advanced=true },
    { id="fg_slipMinMul",               section="field", type="float", target="FieldGroundMudPhysics", key="slipMinMul",          min=0.0, max=1.0, step=0.01, decimals=2, advanced=true },
    { id="fg_slipMaxMul",               section="field", type="float", target="FieldGroundMudPhysics", key="slipMaxMul",          min=0.0, max=1.2, step=0.01, decimals=2, advanced=true },
    { id="fg_extraParticlesEnable",     section="field", type="bool",  target="FieldGroundMudPhysics", key="extraParticlesEnable" },
    { id="fg_extraParticlesPreset",     section="field", type="float", target="FieldGroundMudPhysics", key="extraParticlesPreset", min=1, max=5, step=1, decimals=0 },
    { id="fg_dirtEnable",               section="field", type="bool",  target="FieldGroundMudPhysics", key="dirtEnable" },
    { id="fg_dirtColorUseSprayLayers",  section="field", type="bool",  target="FieldGroundMudPhysics", key="dirtColorUseSprayLayers" },
    { id="fg_dirtMinEffMud",            section="field", type="float", target="FieldGroundMudPhysics", key="dirtMinEffMud",        min=0.0, max=1.0, step=0.01,  decimals=2, advanced=true },
    { id="fg_dirtWetnessMin",           section="field", type="float", target="FieldGroundMudPhysics", key="dirtWetnessMin",       min=0.0, max=1.0, step=0.01,  decimals=2, advanced=true },
    { id="fg_dirtBodyPerSec",           section="field", type="float", target="FieldGroundMudPhysics", key="dirtBodyPerSec",       min=0.0, max=1.0, step=0.001, decimals=3, advanced=true },
    { id="fg_wheelBrakeBase",           section="field", type="float", target="FieldGroundMudPhysics", key="wheelBrakeBase",       min=0.0, max=15.0, step=0.01, decimals=2, menuHidden=true },
    { id="fg_wheelBrakeFromSink",       section="field", type="float", target="FieldGroundMudPhysics", key="wheelBrakeFromSink",   min=0.0, max=15.0, step=0.01, decimals=2, menuHidden=true },
    { id="fg_wheelBrakeFromSlip",       section="field", type="float", target="FieldGroundMudPhysics", key="wheelBrakeFromSlip",   min=0.0, max=15.0, step=0.01, decimals=2, menuHidden=true },
	-- бонус от ширины
	{ id="fg_widthBonusEnable",         section="field", type="bool",  target="FieldGroundMudPhysics", key="widthBonusEnable", advanced=true },
    { id="fg_widthBonusRefWidth",       section="field", type="float", target="FieldGroundMudPhysics", key="widthBonusRefWidth",   min=0.10, max=2.50, step=0.01, decimals=2, advanced=true },
    { id="fg_widthBonusStrength",       section="field", type="float", target="FieldGroundMudPhysics", key="widthBonusStrength",   min=0.0, max=5.0,  step=0.01, decimals=2, advanced=true },
    { id="fg_widthBonusMax",            section="field", type="float", target="FieldGroundMudPhysics", key="widthBonusMax",        min=0.0, max=0.95, step=0.01, decimals=2, advanced=true },
    -- -----------------------------------------------------
    -- FieldGroundMudPhysics (Скрыто)
    -- -----------------------------------------------------
    { id="fgp01_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp01_mud", profile=1, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp01_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp01_wetMul", profile=1, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp01_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp01_sinkMul", profile=1, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp01_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp01_brakeMul", profile=1, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp01_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp01_motorMul", profile=1, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp01_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp01_radiusMinFactor", profile=1, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp01_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp01_dirtMul", profile=1, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp01_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp01_slip", profile=1, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp01_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp01_fxExtra", profile=1 },
    { id="fgp01_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp01_permaStuck", profile=1 },
    { id="fgp02_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp02_mud", profile=2, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp02_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp02_wetMul", profile=2, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp02_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp02_sinkMul", profile=2, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp02_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp02_brakeMul", profile=2, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp02_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp02_motorMul", profile=2, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp02_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp02_radiusMinFactor", profile=2, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp02_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp02_dirtMul", profile=2, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp02_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp02_slip", profile=2, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp02_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp02_fxExtra", profile=2 },
    { id="fgp02_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp02_permaStuck", profile=2 },
    { id="fgp03_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp03_mud", profile=3, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp03_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp03_wetMul", profile=3, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp03_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp03_sinkMul", profile=3, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp03_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp03_brakeMul", profile=3, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp03_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp03_motorMul", profile=3, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp03_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp03_radiusMinFactor", profile=3, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp03_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp03_dirtMul", profile=3, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp03_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp03_slip", profile=3, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp03_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp03_fxExtra", profile=3 },
    { id="fgp03_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp03_permaStuck", profile=3 },
    { id="fgp04_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp04_mud", profile=4, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp04_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp04_wetMul", profile=4, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp04_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp04_sinkMul", profile=4, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp04_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp04_brakeMul", profile=4, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp04_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp04_motorMul", profile=4, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp04_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp04_radiusMinFactor", profile=4, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp04_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp04_dirtMul", profile=4, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp04_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp04_slip", profile=4, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp04_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp04_fxExtra", profile=4 },
    { id="fgp04_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp04_permaStuck", profile=4 },
    { id="fgp05_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp05_mud", profile=5, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp05_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp05_wetMul", profile=5, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp05_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp05_sinkMul", profile=5, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp05_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp05_brakeMul", profile=5, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp05_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp05_motorMul", profile=5, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp05_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp05_radiusMinFactor", profile=5, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp05_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp05_dirtMul", profile=5, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp05_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp05_slip", profile=5, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp05_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp05_fxExtra", profile=5 },
    { id="fgp05_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp05_permaStuck", profile=5 },
    { id="fgp06_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp06_mud", profile=6, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp06_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp06_wetMul", profile=6, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp06_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp06_sinkMul", profile=6, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp06_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp06_brakeMul", profile=6, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp06_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp06_motorMul", profile=6, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp06_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp06_radiusMinFactor", profile=6, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp06_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp06_dirtMul", profile=6, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp06_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp06_slip", profile=6, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp06_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp06_fxExtra", profile=6 },
    { id="fgp06_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp06_permaStuck", profile=6 },
    { id="fgp07_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp07_mud", profile=7, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp07_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp07_wetMul", profile=7, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp07_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp07_sinkMul", profile=7, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp07_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp07_brakeMul", profile=7, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp07_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp07_motorMul", profile=7, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp07_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp07_radiusMinFactor", profile=7, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp07_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp07_dirtMul", profile=7, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp07_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp07_slip", profile=7, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp07_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp07_fxExtra", profile=7 },
    { id="fgp07_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp07_permaStuck", profile=7 },
    { id="fgp08_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp08_mud", profile=8, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp08_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp08_wetMul", profile=8, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp08_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp08_sinkMul", profile=8, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp08_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp08_brakeMul", profile=8, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp08_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp08_motorMul", profile=8, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp08_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp08_radiusMinFactor", profile=8, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp08_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp08_dirtMul", profile=8, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp08_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp08_slip", profile=8, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp08_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp08_fxExtra", profile=8 },
    { id="fgp08_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp08_permaStuck", profile=8 },
    { id="fgp09_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp09_mud", profile=9, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp09_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp09_wetMul", profile=9, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp09_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp09_sinkMul", profile=9, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp09_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp09_brakeMul", profile=9, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp09_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp09_motorMul", profile=9, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp09_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp09_radiusMinFactor", profile=9, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp09_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp09_dirtMul", profile=9, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp09_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp09_slip", profile=9, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp09_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp09_fxExtra", profile=9 },
    { id="fgp09_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp09_permaStuck", profile=9 },
    { id="fgp10_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp10_mud", profile=10, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp10_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp10_wetMul", profile=10, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp10_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp10_sinkMul", profile=10, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp10_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp10_brakeMul", profile=10, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp10_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp10_motorMul", profile=10, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp10_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp10_radiusMinFactor", profile=10, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp10_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp10_dirtMul", profile=10, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp10_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp10_slip", profile=10, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp10_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp10_fxExtra", profile=10 },
    { id="fgp10_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp10_permaStuck", profile=10 },
    { id="fgp11_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp11_mud", profile=11, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp11_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp11_wetMul", profile=11, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp11_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp11_sinkMul", profile=11, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp11_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp11_brakeMul", profile=11, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp11_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp11_motorMul", profile=11, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp11_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp11_radiusMinFactor", profile=11, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp11_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp11_dirtMul", profile=11, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp11_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp11_slip", profile=11, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp11_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp11_fxExtra", profile=11 },
    { id="fgp11_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp11_permaStuck", profile=11 },
    { id="fgp12_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp12_mud", profile=12, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp12_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp12_wetMul", profile=12, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp12_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp12_sinkMul", profile=12, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp12_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp12_brakeMul", profile=12, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp12_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp12_motorMul", profile=12, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp12_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp12_radiusMinFactor", profile=12, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp12_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp12_dirtMul", profile=12, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp12_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp12_slip", profile=12, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp12_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp12_fxExtra", profile=12 },
    { id="fgp12_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp12_permaStuck", profile=12 },
    { id="fgp13_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp13_mud", profile=13, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp13_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp13_wetMul", profile=13, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp13_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp13_sinkMul", profile=13, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp13_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp13_brakeMul", profile=13, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp13_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp13_motorMul", profile=13, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp13_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp13_radiusMinFactor", profile=13, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp13_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp13_dirtMul", profile=13, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp13_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp13_slip", profile=13, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp13_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp13_fxExtra", profile=13 },
    { id="fgp13_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp13_permaStuck", profile=13 },
    { id="fgp14_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp14_mud", profile=14, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp14_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp14_wetMul", profile=14, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp14_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp14_sinkMul", profile=14, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp14_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp14_brakeMul", profile=14, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp14_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp14_motorMul", profile=14, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp14_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp14_radiusMinFactor", profile=14, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp14_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp14_dirtMul", profile=14, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp14_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp14_slip", profile=14, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp14_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp14_fxExtra", profile=14 },
    { id="fgp14_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp14_permaStuck", profile=14 },
    { id="fgp15_mud", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp15_mud", profile=15, min=0.00, max=1.00, step=0.01, decimals=2 },
    { id="fgp15_wetMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp15_wetMul", profile=15, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp15_sinkMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp15_sinkMul", profile=15, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp15_brakeMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp15_brakeMul", profile=15, min=0.00, max=15.00, step=0.01, decimals=2 },
    { id="fgp15_motorMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp15_motorMul", profile=15, min=0.00, max=5.00, step=0.01, decimals=2 },
    { id="fgp15_radiusMinFactor", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp15_radiusMinFactor", profile=15, min=0.12, max=0.98, step=0.01, decimals=2 },
    { id="fgp15_dirtMul", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp15_dirtMul", profile=15, min=0.00, max=3.00, step=0.01, decimals=2 },
    { id="fgp15_slip", section="fieldProfiles", type="float", target="FieldGroundProfiles", key="gp15_slip", profile=15, min=0.30, max=1.20, step=0.01, decimals=2 },
    { id="fgp15_fxExtra", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp15_fxExtra", profile=15 },
    { id="fgp15_permaStuck", section="fieldProfiles", type="bool",  target="FieldGroundProfiles", key="gp15_permaStuck", profile=15 },

}

function MudSystemSettings:getTargetTable(targetName)
    if targetName == "MudPhysics" then
        return rawget(_G, "MudPhysics")
    elseif targetName == "FieldGroundMudPhysics" then
        return rawget(_G, "FieldGroundMudPhysics")
    elseif targetName == "FieldLocalWetness" then
        return rawget(_G, "FieldLocalWetness")
    elseif targetName == "FieldGroundProfiles" then
        self._fgProfilesProxy = self._fgProfilesProxy or setmetatable({}, {
            __index = function(_, k)
                local idxStr, field = string.match(tostring(k), "^gp(%d+)%_(.+)$")
                local idx = tonumber(idxStr)
                local fgm = rawget(_G, "FieldGroundMudPhysics")
                if idx ~= nil and field ~= nil and fgm ~= nil and fgm.groundProfiles ~= nil and fgm.groundProfiles[idx] ~= nil then
                    return fgm.groundProfiles[idx][field]
                end
                return nil
            end,
            __newindex = function(_, k, v)
                local idxStr, field = string.match(tostring(k), "^gp(%d+)%_(.+)$")
                local idx = tonumber(idxStr)
                local fgm = rawget(_G, "FieldGroundMudPhysics")
                if idx ~= nil and field ~= nil and fgm ~= nil and fgm.groundProfiles ~= nil and fgm.groundProfiles[idx] ~= nil then
                    fgm.groundProfiles[idx][field] = v
                end
            end
        })
        return self._fgProfilesProxy
	elseif targetName == "MudWetnessMiniHUD" then
    return rawget(_G, "MudWetnessMiniHUD")
	
    elseif targetName == "TirePressureHUD" then
        return rawget(_G, "TirePressureHUD")

    elseif targetName == "BMP_SoilDryingTuner" then
        return rawget(_G, "BMP_SoilDryingTuner")
	
    elseif targetName == "MudSystemSettings" then
        return self
    elseif targetName == "TirePressureSystem" then
        return rawget(_G, "TirePressureSystem")
    elseif targetName == "PunctureSystem" then
        return rawget(_G, "PunctureSystem")
    elseif targetName == "RoadGarbageSystem" then
        return rawget(_G, "RoadGarbageSystem")
    end
    return nil
end

-- ---------------------------------------------------------
-- XML READ/WRITE
-- ---------------------------------------------------------

function MudSystemSettings:isLocalOnlyItem(item)
    return item ~= nil and (item.localOnly == true or item.section == "hud")
end

function MudSystemSettings:isPresetControlItem(item)
    return item ~= nil and item.id == "mudsys_difficultyPreset"
end

function MudSystemSettings:isDifficultyPresetItem(item)
    if item == nil or item.id == nil then
        return false
    end

    if self.DIFFICULTY_ITEM_IDS[item.id] == true then
        return true
    end

    local profileField = string.match(item.id, "^fgp%d+_(.+)$")
    return profileField ~= nil
        and self.DIFFICULTY_PROFILE_FIELDS[profileField] == true
end

function MudSystemSettings:getIsAdminUser()
    return (g_currentMission ~= nil) and (g_currentMission:getIsServer() or g_currentMission.isMasterUser == true)
end

function MudSystemSettings:getCanReadSharedSettings()
    -- Shared gameplay settings belong exclusively to the authoritative server.
    -- Admin clients edit them through MudSystemSettingsSyncEvent.
    if g_currentMission ~= nil and g_currentMission.getIsServer ~= nil then
        return g_currentMission:getIsServer()
    end
    return g_server ~= nil
end

function MudSystemSettings:getLegacySharedSettingsPath()
    return Utils.getFilename(self.xmlFile, getUserProfileAppPath())
end

function MudSystemSettings:getSharedSettingsPath()
    if g_currentMission ~= nil and g_currentMission.missionInfo ~= nil then
        local directory = g_currentMission.missionInfo.savegameDirectory
        if directory ~= nil and directory ~= "" then
            return directory .. "/" .. self.savegameXmlFile
        end
    end
    return self:getLegacySharedSettingsPath()
end

function MudSystemSettings:getLocalSettingsPath()
    return Utils.getFilename(self.localXmlFile, getUserProfileAppPath())
end

function MudSystemSettings:writeSettingsFile(path, xmlKey, itemFilter)
    local xmlFile = createXMLFile("settings", path, xmlKey)
    if xmlFile == 0 then
        print("[MudSystemSettings] Failed to create XML for saving settings")
        return
    end

    for _, item in ipairs(self.items) do
        if item.id ~= "mudsys_resetAll" and (itemFilter == nil or itemFilter(self, item) == true) then
            local tgt = self:getTargetTable(item.target)
            if tgt ~= nil then
                local value = tgt[item.key]
                local k = string.format("%s.%s#value", xmlKey, item.id)
                if item.type == "bool" then
                    setXMLInt(xmlFile, k, boolTo01(value == true))
                else
                    setXMLFloat(xmlFile, k, tonumber(value) or 0)
                end
            end
        end
    end

    saveXMLFile(xmlFile)
    delete(xmlFile)
end

function MudSystemSettings:readSettingsFile(path, xmlKey, itemFilter)
    if not fileExists(path) then
        return
    end

    local xmlFile = loadXMLFile(xmlKey, path)
    if xmlFile == 0 then
        print("[MudSystemSettings] Failed to load XML, keeping defaults")
        return
    end

    for _, item in ipairs(self.items) do
        if item.id ~= "mudsys_resetAll" and (itemFilter == nil or itemFilter(self, item) == true) then
            local tgt = self:getTargetTable(item.target)
            if tgt ~= nil then
                local k = string.format("%s.%s#value", xmlKey, item.id)
                if hasXMLProperty(xmlFile, k) then
                    if item.type == "bool" then
                        tgt[item.key] = int01ToBool(getXMLInt(xmlFile, k))
                    else
                        local v = getXMLFloat(xmlFile, k)
                        if v ~= nil then
                            tgt[item.key] = clamp(v, item.min or -math.huge, item.max or math.huge)
                        end
                    end
                end
            end
        end
    end

    delete(xmlFile)
end

function MudSystemSettings:writeSharedSettings()
    self:writeSettingsFile(self:getSharedSettingsPath(), self.xmlKey, function(settings, item)
        return not settings:isLocalOnlyItem(item)
    end)
end

function MudSystemSettings:writeLocalSettings()
    self:writeSettingsFile(self:getLocalSettingsPath(), self.localXmlKey, function(settings, item)
        return settings:isLocalOnlyItem(item)
    end)
end

function MudSystemSettings:writeSettings()
    self:writeLocalSettings()

    if self:getCanReadSharedSettings() then
        self:writeSharedSettings()
    end
end

function MudSystemSettings:readSettings()
    self:readLocalSettings()

    if self:getCanReadSharedSettings() then
        self:readSharedSettings()
    end

    self:applyDerivedTargetsAfterLoad()
end

function MudSystemSettings:readSharedSettings()
    local path = self:getSharedSettingsPath()
    if not fileExists(path) then
        -- Preserve existing installations once, then keep settings per savegame.
        local legacyPath = self:getLegacySharedSettingsPath()
        if legacyPath ~= path and fileExists(legacyPath) then
            self:readSettingsFile(legacyPath, self.xmlKey, function(settings, item)
                return not settings:isLocalOnlyItem(item)
            end)
            self:writeSharedSettings()
            print(string.format(
                "[MudSystemSettings] Migrated shared settings to savegame: %s",
                tostring(path)
            ))
            return
        end
        self:writeSharedSettings()
        return
    end

    self:readSettingsFile(path, self.xmlKey, function(settings, item)
        return not settings:isLocalOnlyItem(item)
    end)
end

function MudSystemSettings:readLocalSettings()
    local path = self:getLocalSettingsPath()
    if not fileExists(path) then
        self:writeLocalSettings()
        return
    end

    self:readSettingsFile(path, self.localXmlKey, function(settings, item)
        return settings:isLocalOnlyItem(item)
    end)
end

function MudSystemSettings:applyDerivedTargetsAfterLoad()
    local targets = {
        rawget(_G, "MudWetnessMiniHUD"),
        rawget(_G, "TirePressureHUD"),
        rawget(_G, "MudPhysics"),
        rawget(_G, "FieldLocalWetness"),
        rawget(_G, "FieldGroundMudPhysics"),
        rawget(_G, "BMP_SoilDryingTuner"),
        rawget(_G, "TirePressureSystem"),
        rawget(_G, "PunctureSystem"),
        rawget(_G, "RoadGarbageSystem")
    }

    for _, tgt in ipairs(targets) do
        if tgt ~= nil and type(tgt.onSettingsChanged) == "function" then
            tgt:onSettingsChanged()
        end
    end
end

function MudSystemSettings:getPresetPath(preset)
    preset = math.floor(tonumber(preset) or 3)
    if preset < 1 then preset = 1 end
    if preset > 3 then preset = 3 end

    local relPath = self.PRESET_FILES[preset]
    if relPath == nil then
        return nil, preset
    end

    return Utils.getFilename(relPath, self.path), preset
end

function MudSystemSettings:applyDifficultyPreset(preset, doWrite)
    local path
    path, preset = self:getPresetPath(preset)
    self._applyingPreset = true

    if preset == 3 then
        if self.DEFAULTS == nil then
            self:captureDefaults()
        end

        for _, item in ipairs(self.items or {}) do
            if item.id ~= "mudsys_resetAll"
                and self:isDifficultyPresetItem(item) then
                local defV = self.DEFAULTS[item.id]
                if defV ~= nil then
                    self:applyOneItemValue(item, defV, false)
                end
            end
        end

        self.difficultyPreset = 3
        self:applyDerivedTargetsAfterLoad()
        if self._applyingSnapshot ~= true then
            self:refreshMenuStates()
        end

        if doWrite == true then
            self:writeSettings()
        end

        self._applyingPreset = false
        return true
    end

    if path == nil or not fileExists(path) then
        print(string.format("[MudSystemSettings] Preset XML not found: %s", tostring(path)))
        self._applyingPreset = false
        return false
    end

    self:readSettingsFile(path, self.presetXmlKey, function(settings, item)
        return settings:isDifficultyPresetItem(item)
    end)

    self.difficultyPreset = preset
    self:applyDerivedTargetsAfterLoad()
    if self._applyingSnapshot ~= true then
        self:refreshMenuStates()
    end

    if doWrite == true then
        self:writeSettings()
    end

    self._applyingPreset = false
    return true
end

-- ---------------------------------------------------------
-- MENU INTEGRATION
-- ---------------------------------------------------------

function MudSystemSettings:getStateIndexFromValue(id, value)
    local setting = self.SETTINGS[id]
    if setting == nil or setting.values == nil then
        return 1
    end
    local values = setting.values

    if type(value) == "number" then
        local bestIdx, bestDiff = 1, math.huge
        for i, v in ipairs(values) do
            local d = math.abs(v - value)
            if d < bestDiff then
                bestDiff = d
                bestIdx = i
            end
        end
        return bestIdx
    else
        for i, v in ipairs(values) do
            if v == value then
                return i
            end
        end
        return 1
    end
end

function MudSystemSettings:updateFocusIds(element)
    if not element then return end
    element.focusId = FocusManager:serveAutoFocusId()
    for _, child in pairs(element.elements) do
        self:updateFocusIds(child)
    end
end

function MudSystemSettings:setBinaryOptionStateImmediate(menuOption, enabled)
    if menuOption == nil or menuOption.setState == nil then
        return
    end

    -- BinaryOptionElement animates its slider over several update frames.
    -- Settings may be changed while this page is hidden (for example by the
    -- tire-pressure conflict dialog), so finish that animation immediately.
    menuOption:setState(enabled == true and 2 or 1, false, true)
    if menuOption.sliderMovingDirection ~= nil and menuOption.update ~= nil then
        menuOption:update(0)
    end
end

function MudSystemSettings:addElementAtPosition(element, target, pos)
    if element == nil or target == nil then
        return
    end

    if element.parent ~= nil then
        element.parent:removeElement(element)
    end

    table.insert(target.elements, pos, element)
    element.parent = target
end

function MudSystemSettings:ensureSettingDefinition(item)
    if item.type == "bool" then
        self.SETTINGS[item.id] = {
            values  = { false, true },
            strings = {
                self:getTextSafe("mudsys_option_off", "OFF"),
                self:getTextSafe("mudsys_option_on",  "ON")
            }
        }
        return
    end

    if item.id == "mudsys_difficultyPreset" then
        self.SETTINGS[item.id] = {
            values  = { 1, 2, 3 },
            strings = {
                self:getTextSafe("mudsys_preset_easy",     "Light"),
                self:getTextSafe("mudsys_preset_balanced", "Balanced"),
                self:getTextSafe("mudsys_preset_hard",     "Realistic")
            }
        }
        return
    end

    if item.id == "hud_iconPreset" then
        self.SETTINGS[item.id] = {
            values  = { 1, 2, 3 },
            strings = {
                self:getTextSafe("mudsys_iconPreset_standard", "Standard"),
                self:getTextSafe("mudsys_iconPreset_rmg",      "Icons RMG"),
                self:getTextSafe("mudsys_iconPreset_custom",   "Custom icons")
            }
        }
        return
    end

    if item.id == "hud_moveStep" then
        self.SETTINGS[item.id] = {
            values  = { 1, 5, 10, 25 },
            strings = { "1 px", "5 px", "10 px", "25 px" }
        }
        return
    end

    if item.id == "mp_extraParticlesPreset" or item.id == "fg_extraParticlesPreset" then
        if MudExtraParticlePresets ~= nil then
            self.SETTINGS[item.id] = {
                values = MudExtraParticlePresets.getSettingValues(),
                strings = MudExtraParticlePresets.getSettingStrings()
            }
        else
            self.SETTINGS[item.id] = {
                values = { 1, 2, 3, 4, 5 },
                strings = { "Standard", "Dark standard", "Small particles", "Medium particles", "Medium heavy particles" }
            }
        end
        return
    end
	
    if item.id == "hud_posPreset" then
        self.SETTINGS[item.id] = {
            values  = { 1, 2, 3, 4 },
            strings = {
                self:getTextSafe("mudsys_hudPosPreset_standard",    "Standard"),
                self:getTextSafe("mudsys_hudPosPreset_bottomLeft",  "Bottom-left"),
                self:getTextSafe("mudsys_hudPosPreset_topLeft",     "Top-left"),
                self:getTextSafe("mudsys_hudPosPreset_bottomRight", "Bottom-right")
            }
        }
        return
    end

    self:buildRange(
        item.id,
        item.min,
        item.max,
        item.step,
        item.decimals,
        item.displayScale,
        item.displayDecimals
    )
end

function MudSystemSettings:addMenuSection(settingsLayout, settingsPage, titleKey, titleFallback)
    local header
    for _, elem in ipairs(settingsLayout.elements) do
        if elem.name == "sectionHeader" then
            header = elem:clone(settingsLayout)
            break
        end
    end

    if header == nil then
        header = TextElement.new()
        header:applyProfile("fs25_settingsSectionHeader", true)
        header.name = "sectionHeader"
        settingsLayout:addElement(header)
    end

    header:setText(self:getTextSafe(titleKey, titleFallback))
    header.focusId = FocusManager:serveAutoFocusId()
    if settingsPage ~= nil and settingsPage.controlsList ~= nil then
        table.insert(settingsPage.controlsList, header)
    end
    self.CONTROLS[titleKey] = header
end

function MudSystemSettings:addMenuOption(item)
    if item == nil or item.menuHidden == true then
        return
    end

    local inGameMenu = g_gui.screenControllers[InGameMenu]
    local settingsPage = self._settingsPage or inGameMenu.pageSettings
    local settingsLayout = self._settingsLayout or settingsPage.generalSettingsLayout

    self:ensureSettingDefinition(item)
    local def = self.SETTINGS[item.id]

    local template
    if self._modPage ~= nil then
        template = (#def.values == 2) and self._modPage.mudSystemBinaryPrefab or self._modPage.mudSystemMultiPrefab
    else
        template = (#def.values == 2) and settingsPage.checkWoodHarvesterAutoCutBox or settingsPage.multiVolumeVoiceBox
    end

    if template == nil then
        return
    end

    local menuOptionBox = template:clone(settingsLayout)
    menuOptionBox.id = item.id .. "Box"

    local menuOption = menuOptionBox.elements[1]
    menuOption.id = item.id
    menuOption.target = self
    menuOption:setCallback("onClickCallback", "onMenuOptionChanged")
    menuOption:setDisabled(false)

    local toolTip = menuOption.elements[1]

    local labelKey = "setting_" .. item.id
    local tipKey   = "tooltip_" .. item.id
    local fallbackLabel = item.id

    if item.target == "FieldGroundProfiles" then
        local suffix = string.match(item.id, "^fgp%d+_(.+)$")
        if suffix ~= nil then
            labelKey = "setting_fgp_" .. suffix
            tipKey   = "tooltip_fgp_" .. suffix
            fallbackLabel = suffix
        end
    end

    toolTip:setText(self:getTextSafe(tipKey, ""))

    local label = menuOptionBox.elements[2]
    label:setText(self:getTextSafe(labelKey, fallbackLabel))

    menuOption:setTexts({ unpack(def.strings) })

    if item.id == "mudsys_resetAll" then
        menuOption:setState(1)
    else
        local tgt = self:getTargetTable(item.target)
        local currentValue = tgt and tgt[item.key]
        if item.type == "bool" then
            self:setBinaryOptionStateImmediate(menuOption, currentValue == true)
        else
            menuOption:setState(self:getStateIndexFromValue(item.id, tonumber(currentValue) or 0))
        end
    end

    self.CONTROLS[item.id] = menuOption
    self.CONTROL_BOXES[item.id] = menuOptionBox
    self:updateFocusIds(menuOptionBox)
    if settingsPage ~= nil and settingsPage.controlsList ~= nil then
        table.insert(settingsPage.controlsList, menuOptionBox)
    end
end

function MudSystemSettings:refreshAdvancedSectionVisibility(section, visible)
    local changed = false

    for _, item in ipairs(self.items or {}) do
        if item.section == section and item.advanced == true then
            local box = self.CONTROL_BOXES[item.id]
            if box ~= nil then
                local wasVisible = box.getIsVisibleNonRec == nil
                    or box:getIsVisibleNonRec()
                if wasVisible ~= visible then
                    box:setVisible(visible)
                    changed = true
                end
            end
        end
    end

    local layout = self._settingsLayout
    if layout == nil or not changed then
        return
    end

    layout:updateAbsolutePosition()
    layout:invalidateLayout()

    local settingsFrame = g_inGameMenu ~= nil
        and g_inGameMenu.pageSettings or nil
    if settingsFrame ~= nil then
        if settingsFrame.updateAlternatingElements ~= nil then
            settingsFrame:updateAlternatingElements(layout)
        end
        if settingsFrame.settingsSlider ~= nil then
            settingsFrame.settingsSlider:setDataElement(layout)
            if layout.raiseSliderUpdateEvent ~= nil then
                layout:raiseSliderUpdateEvent()
            end
        end
    end
end

function MudSystemSettings:refreshTirePressureAdvancedVisibility()
    self:refreshAdvancedSectionVisibility(
        "tirePressure",
        self.showTirePressureAdvanced == true
    )
end

function MudSystemSettings:refreshSoilDryingAdvancedVisibility()
    self:refreshAdvancedSectionVisibility(
        "drying",
        self.showSoilDryingAdvanced == true
    )
end

function MudSystemSettings:refreshMudAdvancedVisibility()
    self:refreshAdvancedSectionVisibility(
        "mud",
        self.showMudAdvanced == true
    )
end

function MudSystemSettings:refreshFieldAdvancedVisibility()
    self:refreshAdvancedSectionVisibility(
        "field",
        self.showFieldAdvanced == true
    )
end

function MudSystemSettings:captureDefaults()
    if self._defaultsCaptured == true then return end
    self.DEFAULTS = self.DEFAULTS or {}
    for _, item in ipairs(self.items or {}) do
        if item.id ~= "mudsys_resetAll" then
            local tgt = self:getTargetTable(item.target)
            if tgt ~= nil then
                local v = tgt[item.key]
                if item.type == "bool" then
                    self.DEFAULTS[item.id] = (v == true)
                else
                    self.DEFAULTS[item.id] = tonumber(v) or 0
                end
            end
        end
    end
    self._defaultsCaptured = true
end

function MudSystemSettings:resetToDefaults()
    if self.DEFAULTS == nil then
        self:captureDefaults()
    end

    for _, item in ipairs(self.items or {}) do
        if item.id ~= "mudsys_resetAll" and not self:isPresetControlItem(item) then
            local defV = self.DEFAULTS[item.id]
            if defV ~= nil then
                self:applyOneItemValue(item, defV, false)
            end
        end
    end

    self.difficultyPreset = 3

    self:writeSettings()
end

function MudSystemSettings:_getItemById(id)
    for _, it in ipairs(self.items or {}) do
        if it.id == id then
            return it
        end
    end
    return nil
end

function MudSystemSettings:applyHudMoveStep(step)
    step = math.floor(tonumber(step) or 5)
    if step < 1 then step = 1 end

    self.hudMoveStep = step

    local xItem = self:_getItemById("hud_offsetXPx")
    local yItem = self:_getItemById("hud_offsetYPx")
    if xItem ~= nil then xItem.step = step end
    if yItem ~= nil then yItem.step = step end

    self.SETTINGS["hud_offsetXPx"] = nil
    self.SETTINGS["hud_offsetYPx"] = nil

    for _, id in ipairs({ "hud_offsetXPx", "hud_offsetYPx" }) do
        local it = self:_getItemById(id)
        local opt = self.CONTROLS[id]
        if it ~= nil and opt ~= nil then
            self:ensureSettingDefinition(it)
            local def = self.SETTINGS[id]

            opt:setTexts({ unpack(def.strings) })

            local tgt = self:getTargetTable(it.target)
            local v = tgt and tgt[it.key]
            opt:setState(self:getStateIndexFromValue(id, tonumber(v) or 0))
        end
    end
end

function MudSystemSettings:resetHudPosition()
    local hud = rawget(_G, "MudWetnessMiniHUD")
    if hud == nil then return end

    hud.offsetXPx = 15
    hud.offsetYPx = 0

    for _, id in ipairs({ "hud_offsetXPx", "hud_offsetYPx" }) do
        local it = self:_getItemById(id)
        local opt = self.CONTROLS[id]
        if it ~= nil and opt ~= nil then
            local tgt = self:getTargetTable(it.target)
            local v = tgt and tgt[it.key]
            opt:setState(self:getStateIndexFromValue(id, tonumber(v) or 0))
        end
    end
end

function MudSystemSettings:applyHudPosPreset(preset)
    preset = math.floor(tonumber(preset) or 1)
    if preset < 1 then preset = 1 end
    if preset > 4 then preset = 4 end

    local hud = rawget(_G, "MudWetnessMiniHUD")
    if hud == nil then return end

    -- Координаты пресетов:
    if preset == 1 then
        hud.offsetXPx = 25
        hud.offsetYPx = 0
    elseif preset == 2 then
        hud.offsetXPx = 2130
        hud.offsetYPx = -1305
    elseif preset == 3 then
        hud.offsetXPx = 2125
        hud.offsetYPx = 0
    elseif preset == 4 then
        hud.offsetXPx = 15
        hud.offsetYPx = -1305
    end

    for _, id in ipairs({ "hud_offsetXPx", "hud_offsetYPx" }) do
        local it = self:_getItemById(id)
        local opt = self.CONTROLS[id]
        if it ~= nil and opt ~= nil then
            local tgt = self:getTargetTable(it.target)
            local v = tgt and tgt[it.key]
            opt:setState(self:getStateIndexFromValue(id, tonumber(v) or 0))
        end
    end
end


function MudSystemSettings:refreshMenuStates()
    if self._refreshingMenuStates == true then
        return
    end

    self._refreshingMenuStates = true
    local isAdmin = self:getIsAdminUser()

    for _, item in ipairs(self.items or {}) do
        local menuOption = self.CONTROLS[item.id]
        if menuOption ~= nil then
            if item.id == "mudsys_resetAll" then
                menuOption:setState(1)
            else
                local tgt = self:getTargetTable(item.target)
                local currentValue = tgt and tgt[item.key]

                if item.type == "bool" then
                    self:setBinaryOptionStateImmediate(menuOption, currentValue == true)
                else
                    menuOption:setState(self:getStateIndexFromValue(item.id, tonumber(currentValue) or 0))
                end
            end

            local disabled = (not isAdmin) and (not self:isLocalOnlyItem(item))
            if item.section == "drying"
                and item.id ~= "sdt_enabled"
                and item.id ~= "sdt_showAdvancedSettings" then
                local tuner = rawget(_G, "BMP_SoilDryingTuner")
                disabled = disabled or (tuner ~= nil and tuner.enabled ~= true)
            end
            menuOption:setDisabled(disabled)
        end
    end
    self._refreshingMenuStates = false
    self:refreshTirePressureAdvancedVisibility()
    self:refreshSoilDryingAdvancedVisibility()
    self:refreshMudAdvancedVisibility()
    self:refreshFieldAdvancedVisibility()
end

function MudSystemSettings:onMenuOptionChanged(state, menuOption)
    if self._refreshingMenuStates == true then
        return
    end

    if menuOption == nil then
        return
    end

    local id = menuOption.id

    if id == "mudsys_resetAll" then
        local isAdmin = (g_currentMission ~= nil) and (g_currentMission:getIsServer() or g_currentMission.isMasterUser == true)
        if isAdmin then
            self:resetToDefaults()
            self:refreshMenuStates()
            if MudSystemSettingsSyncEvent ~= nil and MudSystemSettingsSyncEvent.sendSnapshot ~= nil then
                MudSystemSettingsSyncEvent.sendSnapshot(self:collectSnapshot())
            end
        end

        menuOption:setState(1)
        return
    end

    local item
    for _, it in ipairs(self.items or {}) do
        if it.id == id then
            item = it
            break
        end
    end
    if item == nil then
        return
    end

    local isAdmin = self:getIsAdminUser()
    local isLocalOnly = self:isLocalOnlyItem(item)
    if not isAdmin and not isLocalOnly then
        self:refreshMenuStates()
        return
    end

    local def = self.SETTINGS[id]
    if def == nil or def.values == nil then
        return
    end

    local s = state
    if menuOption.getState ~= nil then
        s = menuOption:getState()
    end

    if type(s) == "boolean" then
        s = (s and 2 or 1)
    elseif type(s) == "number" then
        if s == 0 then
            s = 1
        end
        if #def.values == 2 and def.values[s] == nil and def.values[s + 1] ~= nil then
            s = s + 1
        end
    else
        s = 1
    end

    local value = def.values[s]

    if value == nil and item.type == "bool" then
        value = (s == 2)
    end

    self:applyOneItemValue(item, value, true)
    if item.id == "sdt_enabled" then
        self:refreshMenuStates()
    elseif item.id == "sdt_showAdvancedSettings" then
        self:refreshSoilDryingAdvancedVisibility()
    elseif item.id == "tp_showAdvancedSettings" then
        self:refreshTirePressureAdvancedVisibility()
    elseif item.id == "mp_showAdvancedSettings" then
        self:refreshMudAdvancedVisibility()
    elseif item.id == "fg_showAdvancedSettings" then
        self:refreshFieldAdvancedVisibility()
    end

    if (not isLocalOnly) and isAdmin
        and self._applyingPreset ~= true and self._applyingSnapshot ~= true
        and MudSystemSettingsSyncEvent ~= nil
        and MudSystemSettingsSyncEvent.sendSnapshot ~= nil then
        MudSystemSettingsSyncEvent.sendSnapshot(self:collectSnapshot())
    end
end

function MudSystemSettings:restoreSharedDefaults()
    if self.DEFAULTS == nil then self:captureDefaults() end
    self._applyingSnapshot = true
    for _, item in ipairs(self.items or {}) do
        if item.id ~= "mudsys_resetAll" and not self:isLocalOnlyItem(item) then
            local value = self.DEFAULTS[item.id]
            local tgt = self:getTargetTable(item.target)
            if value ~= nil and tgt ~= nil then
                if item.type == "bool" then
                    tgt[item.key] = value == true
                else
                    tgt[item.key] = tonumber(value) or 0
                end
            end
        end
    end
    self._applyingSnapshot = false
end

function MudSystemSettings:applyOneItemValue(item, value, doWrite)
    local tgt = self:getTargetTable(item.target)

    if item.id == "mudsys_difficultyPreset" then
        local preset = math.floor(tonumber(value) or 3)
        if preset < 1 then preset = 1 end
        if preset > 3 then preset = 3 end

        self.difficultyPreset = preset
        self:applyDifficultyPreset(preset, doWrite == true)

        return
    end

    if item.id == "hud_moveStep" then
        local step = tonumber(value) or 5
        if tgt ~= nil then
            tgt[item.key] = step
        end
        self:applyHudMoveStep(step)

        if doWrite == true then
            self:writeSettings()
        end

        if self._applyingPreset ~= true and self._applyingSnapshot ~= true and tgt ~= nil and type(tgt.onSettingsChanged) == "function" then
            tgt:onSettingsChanged()
        end
        return
    end

    if item.id == "hud_posPreset" then
        local preset = tonumber(value) or 1
        if tgt ~= nil then
            tgt[item.key] = preset
        end

        self:applyHudPosPreset(preset)

        if doWrite == true then
            self:writeSettings()
        end

        if self._applyingPreset ~= true and self._applyingSnapshot ~= true and tgt ~= nil and type(tgt.onSettingsChanged) == "function" then
            tgt:onSettingsChanged()
        end
        return
    end

    if item.id == "hud_resetPos" then
        self:resetHudPosition()

        if tgt ~= nil then
            tgt[item.key] = false
        end

        local ctrl = self.CONTROLS[item.id]
        if ctrl ~= nil then
            ctrl:setState(1)
        end

        if doWrite == true then
            self:writeSettings()
        end

        return
    end

    -- -----------------------------------------------------
    -- 2) DEFAULT ASSIGNMENT
    -- -----------------------------------------------------
    if tgt ~= nil then
        if item.type == "bool" then
            tgt[item.key] = (value == true)
        else
            tgt[item.key] = clamp(tonumber(value) or 0, item.min or -math.huge, item.max or math.huge)
        end
    end

    if doWrite == true then
        self:writeSettings()
    end

    -- -----------------------------------------------------
    -- 3) NOTIFY TARGETS
    -- -----------------------------------------------------
    if self._applyingPreset ~= true and self._applyingSnapshot ~= true and tgt ~= nil and type(tgt.onSettingsChanged) == "function" then
        tgt:onSettingsChanged()
    end

    if self._applyingPreset ~= true and self._applyingSnapshot ~= true and item.target == "FieldGroundProfiles" then
        local fgm = rawget(_G, "FieldGroundMudPhysics")
        if fgm ~= nil and type(fgm.onSettingsChanged) == "function" then
            fgm:onSettingsChanged()
        end
    end
end

function MudSystemSettings:collectSnapshot()
    local snap = {}
    for i, item in ipairs(self.items) do
        if item.id == "mudsys_resetAll" or self:isLocalOnlyItem(item) then
            snap[i] = false
        else
            local tgt = self:getTargetTable(item.target)
            local v = tgt and tgt[item.key]
            if item.type == "bool" then
                snap[i] = (v == true)
            else
                snap[i] = tonumber(v) or 0
            end
        end
    end
    return snap
end

function MudSystemSettings:applySnapshot(snapshot, doWrite)
    if snapshot == nil then return end

    self._applyingSnapshot = true
    for i, item in ipairs(self.items) do
        if item.id ~= "mudsys_resetAll" and not self:isLocalOnlyItem(item) then
            local v = snapshot[i]
            if item.id == "mudsys_difficultyPreset" then
                local preset = math.floor(tonumber(v) or 3)
                if preset < 1 then preset = 1 end
                if preset > 3 then preset = 3 end
                self.difficultyPreset = preset
            elseif item.type == "bool" then
                self:applyOneItemValue(item, v == true, false)
            else
                self:applyOneItemValue(item, tonumber(v) or 0, false)
            end
        end
    end
    self._applyingSnapshot = false

    self:applyDerivedTargetsAfterLoad()
    self:refreshMenuStates()

    if doWrite == true then
        self:writeSettings()
    end
end

function MudSystemSettings:markServerSnapshotReceived()
    local wasReceived = self._mpSyncReceived == true
    self._mpSyncReceived = true
    self._mpWantsRequest = false
    self._mpRequestSent = true
    self._mpRequestRetryTimer = 0

    if not wasReceived then
        print("[MudSystemSettings] Authoritative server settings received")
    end
end

function MudSystemSettings:sendServerSnapshotToConnection(connection)
    if g_server == nil or connection == nil
        or connection.sendEvent == nil
        or MudSystemSettingsSyncEvent == nil
        or MudSystemSettingsSyncEvent.new == nil then
        return false
    end

    connection:sendEvent(MudSystemSettingsSyncEvent.new(
        false, self:collectSnapshot()
    ))
    print("[MudSystemSettings] Authoritative settings sent to joining client")
    return true
end

function MudSystemSettings:buildMenu()
    return self:buildMenuForSettingsPage(nil)
end

function MudSystemSettings:buildMenuForSettingsPage(settingsPage)
    if self._menuBuilt == true then
        return true
    end

    if settingsPage == nil then
        local inGameMenu = g_inGameMenu
        if inGameMenu == nil and g_gui ~= nil and g_gui.screenControllers ~= nil then
            inGameMenu = g_gui.screenControllers[InGameMenu]
        end
        if inGameMenu == nil or inGameMenu.pageSettings == nil then
            return false
        end
        settingsPage = inGameMenu.pageSettings
    end

    if settingsPage.subCategoryTabs == nil or settingsPage.subCategoryPages == nil or settingsPage.subCategoryBox == nil then
        return false
    end

    local guiFile = Utils.getFilename("gui/mudSystemSettingsPage.xml", self.path)
    if not fileExists(guiFile) then
        Logging.warning("[MudSystemSettings] Missing settings page GUI '%s'", guiFile)
        return false
    end

    local guiName = "MudSystemSettingsFrame"
    if g_gui.guis ~= nil and g_gui.guis[guiName] ~= nil then
        pcall(function()
            if g_gui.unloadGui ~= nil then
                g_gui:unloadGui(guiName)
            end
        end)
        g_gui.guis[guiName] = nil
    end

    local modPage = MudSystemSettingsPage.new()
    local loadOk = pcall(function()
        g_gui:loadGui(guiFile, guiName, modPage)
    end)

    if not loadOk then
        Logging.warning("[MudSystemSettings] loadGui threw while loading settings page GUI '%s'", guiFile)
        return false
    end

    if g_gui.guis ~= nil and g_gui.guis[guiName] ~= nil and g_gui.guis[guiName].target ~= nil then
        modPage = g_gui.guis[guiName].target
    end

    if modPage.mudSystemPage == nil or modPage.mudSystemTab == nil or modPage.mudSystemSettingsLayout == nil then
        Logging.warning("[MudSystemSettings] Settings page GUI is missing required controls")
        return false
    end

    local pos = #settingsPage.subCategoryTabs + 1
    self._modSubCategoryIndex = pos
    self._modSubCategoryState = pos
    self._modPage = modPage
    self._settingsPage = modPage
    self._settingsLayout = modPage.mudSystemSettingsLayout
    self.name = settingsPage.name

    self:addElementAtPosition(modPage.mudSystemPage, settingsPage.subCategoryPages[1].parent, pos)
    self:addElementAtPosition(modPage.mudSystemTab, settingsPage.subCategoryBox, pos)
    settingsPage:updateAbsolutePosition()

    modPage.mudSystemPage:setTarget(settingsPage, modPage.mudSystemPage.target)
    modPage.mudSystemTab:setTarget(settingsPage, modPage.mudSystemTab.target)

    settingsPage.subCategoryPages[pos] = modPage.mudSystemPage
    settingsPage.subCategoryTabs[pos] = modPage.mudSystemTab

    if settingsPage.subCategoryPaging ~= nil then
        if settingsPage.subCategoryPaging.addText ~= nil then
            settingsPage.subCategoryPaging:addText(pos)
        elseif settingsPage.subCategoryPaging.texts ~= nil then
            table.insert(settingsPage.subCategoryPaging.texts, tostring(pos))
            settingsPage.subCategoryPaging:setTexts(settingsPage.subCategoryPaging.texts)
        end
    end

    settingsPage.subCategoryBox:invalidateLayout()
    if settingsPage.subCategoryPaging ~= nil and settingsPage.subCategoryBox.maxFlowSize ~= nil then
        settingsPage.subCategoryPaging:setSize(settingsPage.subCategoryBox.maxFlowSize + 140 * g_pixelSizeScaledX)
    end
    settingsPage:updateAbsolutePosition()

    if FocusManager ~= nil then
        local currentGui = FocusManager.currentGui
        FocusManager:setGui(settingsPage.name)
        FocusManager:removeElement(modPage.mudSystemPage)
        FocusManager:removeElement(modPage.mudSystemTab)
        FocusManager:loadElementFromCustomValues(modPage.mudSystemPage)
        FocusManager:loadElementFromCustomValues(modPage.mudSystemTab)
        FocusManager:setGui(currentGui)
    end

    InGameMenuSettingsFrame.SUB_CATEGORY.MUD_SYSTEM_PHYSICS = pos
    InGameMenuSettingsFrame.HEADER_SLICES[pos] = "gui.icon_options_generalSettings2"
    InGameMenuSettingsFrame.HEADER_TITLES[pos] = "ui_settings"

    local settingsLayout = self._settingsLayout

    -- Presets
    self:addMenuSection(settingsLayout, self._settingsPage, "menu_MudSystemPresets_TITLE", "Presets")
    for _, item in ipairs(self.items) do
        if item.section == "preset" then
            self:addMenuOption(item)
        end
    end

    -- General
    self:addMenuSection(settingsLayout, self._settingsPage, "menu_MudSystemGeneral_TITLE", "General")
    for _, item in ipairs(self.items) do
        if item.section == "general" then
            self:addMenuOption(item)
        end
    end

    -- Tire pressure system
    self:addMenuSection(settingsLayout, self._settingsPage, "menu_TirePressureSystem_TITLE", "Tire Pressure System")
    for _, item in ipairs(self.items) do
        if item.section == "tirePressure" then
            self:addMenuOption(item)
        end
    end

    -- Wheel punctures and field repair
    self:addMenuSection(settingsLayout, self._settingsPage, "menu_PunctureSystem_TITLE", "Wheel Punctures")
    for _, item in ipairs(self.items) do
        if item.section == "puncture" then
            self:addMenuOption(item)
        end
    end

    -- Local field wetness and field mud info HUD
    self:addMenuSection(settingsLayout, self._settingsPage, "menu_FieldLocalWetness_TITLE", "Field Local Wetness")
    for _, item in ipairs(self.items) do
        if item.section == "localWetness" then
            self:addMenuOption(item)
        end
    end

    -- HUD
    self:addMenuSection(settingsLayout, self._settingsPage, "menu_MudHUD_TITLE", "Mud HUD")
    for _, item in ipairs(self.items) do
        if item.section == "hud" then
            self:addMenuOption(item)
        end
    end

    -- Soil Drying Tuner
    self:addMenuSection(settingsLayout, self._settingsPage, "menu_SoilDryingTuner_TITLE", "Soil Moisture and Drying")
    for _, item in ipairs(self.items) do
        if item.section == "drying" then
            self:addMenuOption(item)
        end
    end

    -- Mud System Physics
    self:addMenuSection(settingsLayout, self._settingsPage, "menu_MudSystemPhysics_TITLE", "Mud System Physics")
    for _, item in ipairs(self.items) do
        if item.section == "mud" then
            self:addMenuOption(item)
        end
    end

    -- Field Ground Mud Physics
    self:addMenuSection(settingsLayout, self._settingsPage, "menu_FieldGroundMudPhysics_TITLE", "Field Ground Mud Physics")
    for _, item in ipairs(self.items) do
        if item.section == "field" then
            self:addMenuOption(item)
        end
    end

    -- FieldGroundMudPhysics.groundProfiles
    -- (СКРЫТО)
    if MudSystemSettings.showFieldGroundProfiles == true then
        self:addMenuSection(settingsLayout, self._settingsPage, "menu_FieldGroundProfiles_TITLE", "Field Ground Profiles")

        local fgm = rawget(_G, "FieldGroundMudPhysics")
        if fgm ~= nil and fgm.groundProfiles ~= nil then
            for i, prof in ipairs(fgm.groundProfiles) do
                local pn = (prof ~= nil and prof.name) or tostring(i)
                local titleKey = string.format("menu_fgprof_%02d_TITLE", i)
                local titleFallback = string.format("Profile %02d: %s", i, tostring(pn))
                self:addMenuSection(settingsLayout, self._settingsPage, titleKey, titleFallback)

                for _, item in ipairs(self.items) do
                    if item.section == "fieldProfiles" and item.profile == i then
                        self:addMenuOption(item)
                    end
                end
            end
        else
            for _, item in ipairs(self.items) do
                if item.section == "fieldProfiles" then
                    self:addMenuOption(item)
                end
            end
        end
    end

    if modPage.mudSystemSectionHeaderPrefab ~= nil then
        modPage.mudSystemSectionHeaderPrefab:delete()
    end
    if modPage.mudSystemBinaryPrefab ~= nil then
        modPage.mudSystemBinaryPrefab:delete()
    end
    if modPage.mudSystemMultiPrefab ~= nil then
        modPage.mudSystemMultiPrefab:delete()
    end

    self:refreshTirePressureAdvancedVisibility()
    self:refreshSoilDryingAdvancedVisibility()
    self:refreshMudAdvancedVisibility()
    self:refreshFieldAdvancedVisibility()
    settingsLayout:updateAbsolutePosition()
    settingsLayout:invalidateLayout()
    if settingsLayout.scrollTo ~= nil then
        settingsLayout:scrollTo(0, true)
    end

    if settingsPage.subCategoryPaging ~= nil and self._subCategoryPagingWrapped ~= true then
        local oldOnClickCallback = settingsPage.subCategoryPaging.onClickCallback
        settingsPage.subCategoryPaging.onClickCallback = function(frame, state)
            local retValue
            if oldOnClickCallback ~= nil then
                retValue = oldOnClickCallback(frame, state)
            end
            if MudSystemSettings ~= nil then
                MudSystemSettings:updateMudSettingsSubCategory(frame, state)
            end
            return retValue
        end
        self._subCategoryPagingWrapped = true
    end

    self._menuBuilt = true
    return true
end

-- ---------------------------------------------------------
-- LIFECYCLE
-- ---------------------------------------------------------

function MudSystemSettings:loadMap(name)
    self:captureDefaults()
    self:restoreSharedDefaults()
    self:readSettings()
    self._mpWantsRequest = false
    self._mpRequestSent = false
    self._mpSyncReceived = false
    self._mpRequestRetryTimer = 0

    if g_currentMission ~= nil then
        FSBaseMission.registerToLoadOnMapFinished(g_currentMission, self)
    end
end

function MudSystemSettings:onLoadMapFinished()
    local isServer = (g_currentMission ~= nil) and g_currentMission:getIsServer()

    if isServer then
        self:readSettings()
    end

    self._menuBuildPending = not self:buildMenu()
    self._menuBuildRetryTimer = 0

    if not isServer then
        self._mpWantsRequest = true
        self._mpRequestSent  = false
        self._mpSyncReceived = false
        self._mpRequestRetryTimer = 0
    end
end

function MudSystemSettings:onPostLoadMap(mapNode, mapFile)
    if self._menuBuilt ~= true then
        self._menuBuildPending = not self:buildMenu()
        self._menuBuildRetryTimer = 0
    end
end

function MudSystemSettings:onUpdate(dt)
    if self._menuBuilt ~= true and self._menuBuildPending == true then
        self._menuBuildRetryTimer = (self._menuBuildRetryTimer or 0) - (dt or 0)
        if self._menuBuildRetryTimer <= 0 then
            self._menuBuildPending = not self:buildMenu()
            self._menuBuildRetryTimer = 500
        end
    end

    if self._mpWantsRequest and not self._mpSyncReceived then
        self._mpRequestRetryTimer = (self._mpRequestRetryTimer or 0) - (dt or 0)
        if g_client ~= nil and g_client.getServerConnection ~= nil and g_client:getServerConnection() ~= nil then
            if self._mpRequestRetryTimer <= 0
                and MudSystemSettingsSyncEvent ~= nil
                and MudSystemSettingsSyncEvent.sendRequest ~= nil then
                MudSystemSettingsSyncEvent.sendRequest()
                self._mpRequestSent = true
                self._mpRequestRetryTimer = 2000
            end
        end
    end
end

-- ---------------------------------------------------------
-- RESET BUTTON
-- ---------------------------------------------------------

function MudSystemSettings:doResetFromButton()
    local isAdmin = (g_currentMission ~= nil) and (g_currentMission:getIsServer() or g_currentMission.isMasterUser == true)
    if not isAdmin then
        return
    end

    local function doReset()
        self:resetToDefaults()
        self:refreshMenuStates()

        if MudSystemSettingsSyncEvent ~= nil and MudSystemSettingsSyncEvent.sendSnapshot ~= nil then
            MudSystemSettingsSyncEvent.sendSnapshot(self:collectSnapshot())
        end
    end

    if g_gui ~= nil and g_gui.showYesNoDialog ~= nil then
        local title = self:getTextSafe("mudsys_ui_resetTitle", self:getTextSafe("ui_warning", "Warning"))
        local text  = self:getTextSafe("mudsys_ui_resetConfirm", "Reset MudSystem settings to defaults?")

        g_gui:showYesNoDialog({
            title = title,
            text = text,
            callback = function(_, yes)
                if yes then
                    doReset()
                end
            end
        })
    else
        doReset()
    end
end

function MudSystemSettings:_pickFreeSettingsExtraAction(settingsFrame)
    local used = {}

    if settingsFrame ~= nil and settingsFrame.menuButtonInfo ~= nil then
        for _, b in ipairs(settingsFrame.menuButtonInfo) do
            if b ~= nil and b.inputAction ~= nil then
                used[b.inputAction] = true
            end
        end
    end

    local candidates = {
        InputAction.MENU_EXTRA_1,
        InputAction.MENU_EXTRA_2,
        InputAction.MENU_EXTRA_3,
        InputAction.MENU_EXTRA_4
    }

    for _, a in ipairs(candidates) do
        if a ~= nil and used[a] ~= true then
            return a
        end
    end

    return nil
end

function MudSystemSettings:ensureResetButton(settingsFrame)
    if settingsFrame == nil or settingsFrame.menuButtonInfo == nil then
        return
    end

    local shouldShow = self:getIsMudSettingsPageSelected(settingsFrame) and self:getIsAdminUser()
    if not shouldShow then
        if self._resetButton ~= nil then
            for i = #settingsFrame.menuButtonInfo, 1, -1 do
                if settingsFrame.menuButtonInfo[i] == self._resetButton then
                    table.remove(settingsFrame.menuButtonInfo, i)
                    settingsFrame:setMenuButtonInfoDirty()
                    break
                end
            end
        end
        return
    end

    self._resetButton = self._resetButton or {
        inputAction = self:_pickFreeSettingsExtraAction(settingsFrame),
        text = self:getTextSafe("mudsys_ui_resetAll", "Reset MudSystem settings"),
        callback = function()
            if MudSystemSettings ~= nil then
                MudSystemSettings:doResetFromButton()
            end
        end,
        showWhenPaused = true
    }

    local desiredAction = self:_pickFreeSettingsExtraAction(settingsFrame)
    if self._resetButton.inputAction ~= desiredAction then
        self._resetButton.inputAction = desiredAction
    end

    for _, b in ipairs(settingsFrame.menuButtonInfo) do
        if b == self._resetButton then
            return
        end
    end

    table.insert(settingsFrame.menuButtonInfo, self._resetButton)
    settingsFrame:setMenuButtonInfoDirty()
end

function MudSystemSettings:getIsMudSettingsPageSelected(settingsFrame)
    if settingsFrame == nil or settingsFrame.subCategoryPaging == nil or self._modSubCategoryIndex == nil then
        return false
    end

    local state = settingsFrame.subCategoryPaging:getState()
    local subCategoryIndex = settingsFrame.subCategoryPaging.texts[state]
    return tonumber(subCategoryIndex) == self._modSubCategoryIndex
end

function MudSystemSettings:refreshMudSettingsSubCategoryState(settingsFrame)
    if settingsFrame == nil or settingsFrame.subCategoryPaging == nil or settingsFrame.subCategoryPaging.texts == nil or self._modSubCategoryIndex == nil then
        return
    end

    self._modSubCategoryState = nil
    for state, subCategoryIndex in ipairs(settingsFrame.subCategoryPaging.texts) do
        if tonumber(subCategoryIndex) == self._modSubCategoryIndex then
            self._modSubCategoryState = state
            return
        end
    end
end

function MudSystemSettings:updateMudSettingsPageVisibility(settingsFrame)
    if self._modPage == nil then
        return
    end

    self:refreshMudSettingsSubCategoryState(settingsFrame)

    if self._modPage.mudSystemSettingsLayout ~= nil then
        self._modPage.mudSystemSettingsLayout:setVisible(true)
    end
    if self._modPage.mudSystemSettingsSeparator ~= nil then
        self._modPage.mudSystemSettingsSeparator:setVisible(true)
    end
    if self._modPage.mudSystemNoPermissionText ~= nil then
        self._modPage.mudSystemNoPermissionText:setVisible(false)
    end

    self:refreshMenuStates()

    if settingsFrame ~= nil and settingsFrame.updateAlternatingElements ~= nil then
        settingsFrame:updateAlternatingElements(self._modPage.mudSystemSettingsLayout)
    end
end

function MudSystemSettings:updateMudSettingsSubCategory(settingsFrame)
    if settingsFrame == nil or self._modPage == nil then
        return
    end

    if not self:getIsMudSettingsPageSelected(settingsFrame) then
        self._lastMudSubCategoryState = nil
        self._lastMudLayoutElementCount = nil
        return
    end

    local layout = self._modPage.mudSystemSettingsLayout
    if layout == nil then
        return
    end

    local currentState = settingsFrame.subCategoryPaging ~= nil and settingsFrame.subCategoryPaging:getState() or nil
    local elementCount = layout.elements ~= nil and #layout.elements or 0
    if self._lastMudSubCategoryState == currentState and self._lastMudLayoutElementCount == elementCount then
        return
    end

    self._lastMudSubCategoryState = currentState
    self._lastMudLayoutElementCount = elementCount

    layout:updateAbsolutePosition()
    layout:invalidateLayout()

    if settingsFrame.settingsSlider ~= nil then
        settingsFrame.settingsSlider:setDataElement(layout)
        if layout.raiseSliderUpdateEvent ~= nil then
            layout:raiseSliderUpdateEvent()
        end
    end

    if settingsFrame.categoryHeaderIcon ~= nil then
        settingsFrame.categoryHeaderIcon:setImageSlice(nil, "gui.icon_options_generalSettings2")
    end
    if settingsFrame.categoryHeaderText ~= nil then
        settingsFrame.categoryHeaderText:setText("Mud System Physics")
    end

    local firstFocusable = layout:findFirstFocusable(true)
    local lastElement = layout.elements ~= nil and layout.elements[#layout.elements] or nil
    local lastFocusable = lastElement ~= nil and lastElement.elements ~= nil and lastElement.elements[1] or firstFocusable

    if firstFocusable ~= nil and lastFocusable ~= nil then
        FocusManager:linkElements(settingsFrame.subCategoryPaging, FocusManager.TOP, lastFocusable)
        FocusManager:linkElements(settingsFrame.subCategoryPaging, FocusManager.BOTTOM, firstFocusable)
    end
end

if InGameMenuSettingsFrame ~= nil and Utils ~= nil and Utils.appendedFunction ~= nil then
    InGameMenuSettingsFrame.onFrameOpen = Utils.appendedFunction(InGameMenuSettingsFrame.onFrameOpen, function(settingsFrame)
        if MudSystemSettings == nil then return end

        if MudSystemSettings._menuBuilt ~= true then
            MudSystemSettings._menuBuildPending = not MudSystemSettings:buildMenuForSettingsPage(settingsFrame)
        end

        MudSystemSettings._lastMudSubCategoryState = nil
        MudSystemSettings._lastMudLayoutElementCount = nil
        MudSystemSettings:updateMudSettingsPageVisibility(settingsFrame)
    end)

    InGameMenuSettingsFrame.updateSubCategoryPages = Utils.appendedFunction(InGameMenuSettingsFrame.updateSubCategoryPages, function(self)
        if MudSystemSettings == nil then return end

        MudSystemSettings:updateMudSettingsSubCategory(self)
    end)

    InGameMenuSettingsFrame.updateButtons = Utils.appendedFunction(InGameMenuSettingsFrame.updateButtons, function(self)
        if MudSystemSettings == nil then return end
        MudSystemSettings:ensureResetButton(self)
    end)
end

if FocusManager ~= nil and Utils ~= nil and Utils.appendedFunction ~= nil then
    FocusManager.setGui = Utils.appendedFunction(FocusManager.setGui, function(_, gui)
        if gui == "ingameMenuSettings" and MudSystemSettings ~= nil then
            for _, control in pairs(MudSystemSettings.CONTROLS or {}) do
                if control ~= nil and (not control.focusId or not FocusManager.currentFocusData.idToElementMapping[control.focusId]) then
                    if not FocusManager:loadElementFromCustomValues(control, nil, nil, false, false) then
                        Logging.warning("[MudSystemSettings] Could not register control %s with the focus manager", control.id or control.name or tostring(control.focusId))
                    end
                end
            end
            if MudSystemSettings._modPage ~= nil and MudSystemSettings._modPage.mudSystemSettingsLayout ~= nil then
                MudSystemSettings._modPage.mudSystemSettingsLayout:invalidateLayout()
            end
        end
    end)
end

local function mudSystemSettingsSavegameHook()
    if g_server ~= nil and MudSystemSettings ~= nil
        and MudSystemSettings.writeSharedSettings ~= nil then
        MudSystemSettings:writeSharedSettings()
    end
end

if FSBaseMission ~= nil and FSBaseMission.saveSavegame ~= nil
    and Utils ~= nil and Utils.appendedFunction ~= nil
    and _G.__MudSystemSettingsSaveHooked ~= true then
    FSBaseMission.saveSavegame = Utils.appendedFunction(
        FSBaseMission.saveSavegame, mudSystemSettingsSavegameHook
    )
    _G.__MudSystemSettingsSaveHooked = true
end

local function mudSystemSettingsConnectionFinishedHook(
    mission, connection, x, y, z, viewDistanceCoeff
)
    if mission == nil or connection == nil or MudSystemSettings == nil then
        return
    end

    -- This hook runs after FSBaseMission has marked the connection ready for
    -- events and sent the vanilla initial state.  Therefore a reconnecting
    -- player receives the authoritative server values even before opening
    -- the settings page and regardless of administrator status.
    if mission.getIsServer ~= nil and mission:getIsServer() then
        MudSystemSettings:sendServerSnapshotToConnection(connection)
    end
end

if FSBaseMission ~= nil and FSBaseMission.onConnectionFinishedLoading ~= nil
    and Utils ~= nil and Utils.appendedFunction ~= nil
    and _G.__MudSystemSettingsJoinSyncHooked ~= true then
    FSBaseMission.onConnectionFinishedLoading = Utils.appendedFunction(
        FSBaseMission.onConnectionFinishedLoading,
        mudSystemSettingsConnectionFinishedHook
    )
    _G.__MudSystemSettingsJoinSyncHooked = true
end
