-- 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 = {}

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

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

MudSystemSettings.xmlKey  = "mudSystemPhysicsSettings"
MudSystemSettings.xmlFile = "modSettings/MudSystemPhysics.xml"

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

-- Настройки каждого слоя земли
-- false = полностью скрыты из меню, но продолжают работать и грузиться из XML
MudSystemSettings.showFieldGroundProfiles = 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)
    local setting = self.SETTINGS[id]
    if setting ~= nil and setting.values ~= nil and setting.strings ~= nil then
        return setting.values, setting.strings
    end

    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, decimals)
    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 = {
	
	-- 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="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" },

    -- { id="mudsys_resetAll",             section="mud", type="bool",  target="MudSystemSettings", key="resetAll" },
	
    -- MudPhysics
    { id="mp_enabled",                  section="mud", type="bool",  target="MudPhysics", key="enabled" },
	
	-- 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" },
    { id="mp_freezeAllLayersInWinterEnable", section="mud", type="bool",  target="MudPhysics", key="freezeAllLayersInWinterEnable" },
    { id="mp_freezeAllLayersTempC",        section="mud", type="float", target="MudPhysics", key="freezeAllLayersTempC", min=-15.0, max=1.0, step=1, decimals=1 },
    { id="mp_extraWheelSinkEnable",     section="mud", type="bool",  target="MudPhysics", key="extraWheelSinkEnable" },
    { id="mp_winterSlipEnable",         section="mud", type="bool",  target="MudPhysics", key="winterSlipEnable" },
    { id="mp_winterSlipTempC",          section="mud", type="float", target="MudPhysics", key="winterSlipTempC",        min=-10.0, max=0.0,  step=1.0,  decimals=0 },
    { id="mp_winterSlipMul",            section="mud", type="float", target="MudPhysics", key="winterSlipMul",          min=0.0,   max=2.0,  step=0.01, decimals=2 },
    { id="mp_normalSlipEnable",         section="mud", type="bool",  target="MudPhysics", key="normalSlipEnable" },
    { id="mp_normalSlipMul",            section="mud", type="float", target="MudPhysics", key="normalSlipMul",          min=0.0,   max=1.0,  step=0.01, decimals=2 },
    { id="mp_rainSlipEnable",           section="mud", type="bool",  target="MudPhysics", key="rainSlipEnable" },
    { id="mp_rainSlipWetnessMin",       section="mud", type="float", target="MudPhysics", key="rainSlipWetnessMin",     min=0.0,   max=1.0,  step=0.01, decimals=2 },
    { id="mp_rainSlipMul",              section="mud", type="float", target="MudPhysics", key="rainSlipMul",            min=0.0,   max=1.0,  step=0.01, decimals=2 },
    { id="mp_rainSlipMaxMul",           section="mud", type="float", target="MudPhysics", key="rainSlipMaxMul",         min=0.0,   max=1.0,  step=0.01, decimals=2 },
    { 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=2 },
    { id="mp_motorLoadEnable",          section="mud", type="bool",  target="MudPhysics", key="motorLoadEnable" },
    { 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 },
    { id="mp_mudVarCell",               section="mud", type="float", target="MudPhysics", key="mudVarCell",             min=0.0,   max=10.0, step=0.01, decimals=2 },
    { id="mp_mudBobAmp",                section="mud", type="float", target="MudPhysics", key="mudBobAmp",             min=0.0,   max=1.0,  step=0.01, decimals=2 },
    { id="mp_extraParticleOnlyWetMud",  section="mud", type="bool",  target="MudPhysics", key="extraParticleOnlyWetMud" },
    { id="mp_extraParticleOffsetY",     section="mud", type="float", target="MudPhysics", key="extraParticleOffsetY",  min=-1.0,  max=1.0,  step=0.01, decimals=2 },
    { 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 },
    { id="mp_dirtWetnessMin",           section="mud", type="float", target="MudPhysics", key="dirtWetnessMin",         min=0.0,   max=1.0,  step=0.01,  decimals=2 },
    { id="mp_dirtBodyPerSec",           section="mud", type="float", target="MudPhysics", key="dirtBodyPerSec",         min=0.0,   max=1.0,  step=0.001, decimals=3 },
    { id="mp_dirtWheelPerSec",          section="mud", type="float", target="MudPhysics", key="dirtWheelPerSec",        min=0.0,   max=1.0,  step=0.001, decimals=3 },
    { id="mp_wheelBrakeEnable",         section="mud", type="bool",  target="MudPhysics", key="wheelBrakeEnable" },
    { id="mp_sinkInSpeed",              section="mud", type="float", target="MudPhysics", key="sinkInSpeed",            min=0.0, max=10.0, step=0.01, decimals=2 },
    { id="mp_sinkOutSpeed",             section="mud", type="float", target="MudPhysics", key="sinkOutSpeed",           min=0.0, max=10.0, step=0.01, decimals=2 },
    
    { id="mp_radiusMinFactor",          section="mud", type="float", target="MudPhysics", key="radiusMinFactor",        min=0.0, max=1.0,  step=0.01, decimals=2 },
    { id="mp_emitMultWetMud",           section="mud", type="float", target="MudPhysics", key="emitMultWetMud",         min=0.0, max=50.0, step=0.01, decimals=2 },
    { id="mp_sizeMultWetMud",           section="mud", type="float", target="MudPhysics", key="sizeMultWetMud",         min=0.0, max=50.0, step=0.01, decimals=2 },
    { id="mp_speedMultWetMud",          section="mud", type="float", target="MudPhysics", key="speedMultWetMud",        min=0.0, max=50.0, step=0.01, decimals=2 },
    { id="mp_wheelBrakeBase",           section="mud", type="float", target="MudPhysics", key="wheelBrakeBase",         min=0.0, max=15.0, step=0.01, decimals=2 },
    { id="mp_wheelBrakeFromSink",       section="mud", type="float", target="MudPhysics", key="wheelBrakeFromSink",     min=0.0, max=15.0, step=0.01, decimals=2 },
    { id="mp_wheelBrakeFromSlip",       section="mud", type="float", target="MudPhysics", key="wheelBrakeFromSlip",     min=0.0, max=15.0, step=0.01, decimals=2 },
	-- бонус от ширины
	{ id="mp_widthBonusEnable",         section="mud", type="bool",  target="MudPhysics", key="widthBonusEnable" },
    { id="mp_widthBonusRefWidth",       section="mud", type="float", target="MudPhysics", key="widthBonusRefWidth",     min=0.10, max=2.50, step=0.01, decimals=2 },
    { id="mp_widthBonusStrength",       section="mud", type="float", target="MudPhysics", key="widthBonusStrength",     min=0.0, max=5.0,  step=0.01, decimals=2 },
    { id="mp_widthBonusMax",            section="mud", type="float", target="MudPhysics", key="widthBonusMax",          min=0.0, max=0.95, step=0.01, decimals=2 },

    -- FieldGroundMudPhysics
    { id="fg_enabled",                  section="field", type="bool",  target="FieldGroundMudPhysics", key="enabled" },
    { id="fg_permaStuckEnable",         section="field", type="bool",  target="FieldGroundMudPhysics", key="permaStuckEnable" },
    { id="fg_showFieldGroundProfiles",  section="field", type="bool",  target="MudSystemSettings", key="showFieldGroundProfiles" },
	{ id="fg_freezeAllLayersInWinterEnable", section="field", type="bool",  target="FieldGroundMudPhysics", key="freezeAllLayersInWinterEnable" },
    { id="fg_freezeAllLayersTempC",          section="field", type="float", target="FieldGroundMudPhysics", key="freezeAllLayersTempC", min=-15.0, max=1.0, step=1, decimals=1 },
    { id="fg_motorLoadEnable",          section="field", type="bool",  target="FieldGroundMudPhysics", key="motorLoadEnable" },
    { id="fg_wheelBrakeEnable",         section="field", type="bool",  target="FieldGroundMudPhysics", key="wheelBrakeEnable" },
    { id="fg_radiusSinkEnable",         section="field", type="bool",  target="FieldGroundMudPhysics", key="radiusSinkEnable" },
    
    { id="fg_radiusMinFactor",          section="field", type="float", target="FieldGroundMudPhysics", key="radiusMinFactor",     min=0.0, max=1.0,  step=0.01, decimals=2 },
    { id="fg_radiusSinkInSpeed",        section="field", type="float", target="FieldGroundMudPhysics", key="radiusSinkInSpeed",   min=0.0, max=2.0, step=0.001, decimals=3 },
    { id="fg_radiusSinkOutSpeed",       section="field", type="float", target="FieldGroundMudPhysics", key="radiusSinkOutSpeed",  min=0.0, max=2.0, step=0.001, decimals=3 },
    { id="fg_slipMinMul",               section="field", type="float", target="FieldGroundMudPhysics", key="slipMinMul",          min=0.0, max=1.0, step=0.01, decimals=2 },
    { id="fg_slipMaxMul",               section="field", type="float", target="FieldGroundMudPhysics", key="slipMaxMul",          min=0.0, max=1.0, step=0.01, decimals=2 },
    { 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_eraseFruitEnable",         section="field", type="bool",  target="FieldGroundMudPhysics", key="eraseFruitEnable" },
	{ id="fg_eraseFoliageOnlyFields",   section="field", type="bool",  target="FieldGroundMudPhysics", key="eraseFoliageOnlyFields" },
    { 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 },
    { id="fg_dirtWetnessMin",           section="field", type="float", target="FieldGroundMudPhysics", key="dirtWetnessMin",       min=0.0, max=1.0, step=0.01,  decimals=2 },
    { id="fg_dirtBodyPerSec",           section="field", type="float", target="FieldGroundMudPhysics", key="dirtBodyPerSec",       min=0.0, max=1.0, step=0.001, decimals=3 },
    { id="fg_dirtWheelPerSec",          section="field", type="float", target="FieldGroundMudPhysics", key="dirtWheelPerSec",      min=0.0, max=1.0, step=0.001, decimals=3 },
    { id="fg_wheelBrakeBase",           section="field", type="float", target="FieldGroundMudPhysics", key="wheelBrakeBase",       min=0.0, max=15.0, step=0.01, decimals=2 },
    { id="fg_wheelBrakeFromSink",       section="field", type="float", target="FieldGroundMudPhysics", key="wheelBrakeFromSink",   min=0.0, max=15.0, step=0.01, decimals=2 },
    { id="fg_wheelBrakeFromSlip",       section="field", type="float", target="FieldGroundMudPhysics", key="wheelBrakeFromSlip",   min=0.0, max=15.0, step=0.01, decimals=2 },
	-- бонус от ширины
	{ id="fg_widthBonusEnable",         section="field", type="bool",  target="FieldGroundMudPhysics", key="widthBonusEnable" },
    { id="fg_widthBonusRefWidth",       section="field", type="float", target="FieldGroundMudPhysics", key="widthBonusRefWidth",   min=0.10, max=2.50, step=0.01, decimals=2 },
    { id="fg_widthBonusStrength",       section="field", type="float", target="FieldGroundMudPhysics", key="widthBonusStrength",   min=0.0, max=5.0,  step=0.01, decimals=2 },
    { id="fg_widthBonusMax",            section="field", type="float", target="FieldGroundMudPhysics", key="widthBonusMax",        min=0.0, max=0.95, step=0.01, decimals=2 },
    -- -----------------------------------------------------
    -- 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 == "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 == "MudSystemSettings" then
        return self
    end
    return nil
end

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

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

function MudSystemSettings:writeSettings()
    local userSettingsFile = self:getUserSettingsPath()
    local xmlFile = createXMLFile("settings", userSettingsFile, self.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" then
            local tgt = self:getTargetTable(item.target)
            if tgt ~= nil then
                local value = tgt[item.key]
                local k = string.format("%s.%s#value", self.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:readSettings()
    local userSettingsFile = self:getUserSettingsPath()
    if not fileExists(userSettingsFile) then
        self:writeSettings()
        self:applyDerivedTargetsAfterLoad()
        return
    end

    local xmlFile = loadXMLFile(self.xmlKey, userSettingsFile)
    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" then
            local tgt = self:getTargetTable(item.target)
            if tgt ~= nil then
                local k = string.format("%s.%s#value", self.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)
    self:applyDerivedTargetsAfterLoad()
end

function MudSystemSettings:applyDerivedTargetsAfterLoad()
    local targets = {
        rawget(_G, "MudWetnessMiniHUD"),
        rawget(_G, "MudPhysics"),
        rawget(_G, "FieldGroundMudPhysics")
    }

    for _, tgt in ipairs(targets) do
        if tgt ~= nil and type(tgt.onSettingsChanged) == "function" then
            tgt:onSettingsChanged()
        end
    end
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: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 == "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)
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)
    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
            menuOption:setState((currentValue == true) and 2 or 1)
        else
            menuOption:setState(self:getStateIndexFromValue(item.id, tonumber(currentValue) or 0))
        end
    end

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

function MudSystemSettings:captureDefaults()
    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
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" then
            local defV = self.DEFAULTS[item.id]
            if defV ~= nil then
                self:applyOneItemValue(item, defV, false)
            end
        end
    end

    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()
    local isAdmin = (g_currentMission ~= nil) and (g_currentMission:getIsServer() or g_currentMission.isMasterUser)

    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
                    menuOption:setState((currentValue == true) and 2 or 1)
                else
                    menuOption:setState(self:getStateIndexFromValue(item.id, tonumber(currentValue) or 0))
                end
            end

            menuOption:setDisabled(not isAdmin)
        end
    end
end

function MudSystemSettings:onMenuOptionChanged(state, menuOption)
    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 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 g_currentMission ~= nil and g_currentMission.missionDynamicInfo ~= nil then
        local isAdmin = g_currentMission:getIsServer() or (g_currentMission.isMasterUser == true)
        if isAdmin and MudSystemSettingsSyncEvent ~= nil and MudSystemSettingsSyncEvent.sendSnapshot ~= nil then
            MudSystemSettingsSyncEvent.sendSnapshot(self:collectSnapshot())
        end
    end
end

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

    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 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 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 tgt ~= nil and type(tgt.onSettingsChanged) == "function" then
        tgt:onSettingsChanged()
    end

    if 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" 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

    for i, item in ipairs(self.items) do
        if item.id ~= "mudsys_resetAll" then
            local v = snapshot[i]
            if item.type == "bool" then
                self:applyOneItemValue(item, v == true, false)
            else
                self:applyOneItemValue(item, tonumber(v) or 0, false)
            end
        end
    end

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

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

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

    local settingsPage = inGameMenu.pageSettings
    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 modPage = MudSystemSettingsPage.new()
    if g_gui:loadGui(guiFile, "MudSystemSettingsFrame", modPage) == nil then
        Logging.warning("[MudSystemSettings] Could not load settings page GUI '%s'", guiFile)
        return false
    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] = "settingspage_title"

    local settingsLayout = self._settingsLayout

    -- 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

    -- 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

    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:readSettings()

    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:buildMenu()

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

function MudSystemSettings:onUpdate(dt)
    if self._mpWantsRequest and not self._mpRequestSent then
        if g_client ~= nil and g_client.getServerConnection ~= nil and g_client:getServerConnection() ~= nil then
            if MudSystemSettingsSyncEvent ~= nil and MudSystemSettingsSyncEvent.sendRequest ~= nil then
                MudSystemSettingsSyncEvent.sendRequest()
            end
            self._mpRequestSent  = true
            self._mpWantsRequest = false
        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
    if not self:getIsMudSettingsPageSelected(settingsFrame) then
        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)

    local isAdmin = (g_currentMission ~= nil) and (g_currentMission:getIsServer() or g_currentMission.isMasterUser == true)
    if self._modPage.mudSystemSettingsLayout ~= nil then
        self._modPage.mudSystemSettingsLayout:setVisible(isAdmin)
    end
    if self._modPage.mudSystemSettingsSeparator ~= nil then
        self._modPage.mudSystemSettingsSeparator:setVisible(isAdmin)
    end
    if self._modPage.mudSystemNoPermissionText ~= nil then
        self._modPage.mudSystemNoPermissionText:setVisible(not isAdmin)
    end

    self:refreshMenuStates()

    if isAdmin and 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 or not self:getIsMudSettingsPageSelected(settingsFrame) then
        return
    end

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

    layout:updateAbsolutePosition()
    layout:invalidateLayout()

    if settingsFrame.settingsSlider ~= nil then
        settingsFrame.settingsSlider:setDataElement(layout)
        if layout.raiseSliderUpdateEvent ~= nil then
            layout:raiseSliderUpdateEvent()
        end
    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

        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
