-- Привилегии, на которые НЕ действует инерция (добавьте свои группы)
local EXCLUDED_USERGROUPS = {
    ["superadmin"] = true,
    ["owner"] = true,
    -- Добавьте другие группы по необходимости
}

-- Функция проверки привилегий и режима администратора
local function IsPlayerExcluded(ply)
    if not IsValid(ply) then return false end
    
    -- Проверка на режим noclip (обычно используется в /adm)
    if ply:GetMoveType() == MOVETYPE_NOCLIP then return true end
    
    -- Проверка различных NWBool переменных для админ-режима
    if ply:GetNWBool("AdminMode") or ply:GetNWBool("admin_mode") or ply:GetNWBool("isAdminMode") then return true end
    if ply:GetNWBool("InAdminMode") or ply:GetNWBool("in_admin_mode") then return true end
    
    -- Проверка на god mode (часто идет с админ-режимом)
    if ply:HasGodMode() then return true end
    
    -- Проверка на привилегии
    return EXCLUDED_USERGROUPS[ply:GetUserGroup()] or false
end

local changeMoveSpeedCVar = CreateConVar( "IC_move_speeds_changespeed", 1, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Whether or not the mod should change movement speed", 0, 1 )
local walkSpeedCVar = CreateConVar( "IC_move_speeds_walkspeed", 190, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 1 )
local sprintSpeedCVar = CreateConVar( "IC_move_speeds_sprintspeed", 320, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 1 )
local slowWalkSpeedCVar = CreateConVar( "IC_move_speeds_slowwalkspeed", 100, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 1 )
local crouchedSpeedCVar = CreateConVar( "IC_move_speeds_crouchedspeed", 72, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 1 )
local ladderSpeedCVar = CreateConVar( "IC_move_speeds_ladderspeed", 70, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 1 )
local duckSpeedCVar = CreateConVar( "IC_move_speeds_duckspeed", 0.4, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 0.01, 1 )
local trueFrictionCVar = CreateConVar( "IC_move_basic_friction", 40, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Amount of true, speed-independent friction to apply", 0, 1000 )
local jumpHeightCVar = CreateConVar( "IC_move_speeds_jump_height", 33, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Desired max jump height", 1, 1000 )
local inertiaCVar = CreateConVar( "IC_move_basic_inertia", 2, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "The divisor for the amount of acceleration and friction. 2 means double the inertia of default gmod", 0.1, 10 )
local landingDragCVar = CreateConVar( "IC_move_limits_landing_drag", 1, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "The amount of extra drag to apply when landing on the ground", 0, 10 )
local airStrafeLimitsCVar = CreateConVar( "IC_move_limits_air_strafing", 1, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Limit air strafing", 0, 1 )
local noJumpBoostCVar = CreateConVar( "IC_move_limits_jump_boost", 1, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Disables speed boost when jumping", 0, 1 )
local jumpDelayCVar = CreateConVar( "IC_move_limits_jump_cooldown", 0, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Adds a delay before you can jump again", 0, 1 )
local sprintRestrictionsCVar = CreateConVar( "IC_move_limits_sprint_restrictions", 1, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Disallows sprinting to the side or backwards", 0, 1 )

local function SetPlyMoveSpeeds(ply, mv)

    ply:SetWalkSpeed( walkSpeedCVar:GetFloat() )
    ply:SetRunSpeed( sprintSpeedCVar:GetFloat() )
    ply:SetSlowWalkSpeed( slowWalkSpeedCVar:GetFloat() )
    ply:SetCrouchedWalkSpeed( crouchedSpeedCVar:GetFloat() / walkSpeedCVar:GetFloat() )
    ply:SetLadderClimbSpeed( ladderSpeedCVar:GetFloat() )
    ply:SetDuckSpeed( duckSpeedCVar:GetFloat() )
    ply:SetUnDuckSpeed( duckSpeedCVar:GetFloat()  )
    ply:SetJumpPower( math.sqrt( 2 * GetConVar( "sv_gravity" ):GetFloat() * jumpHeightCVar:GetFloat() ) )

end

local function CanSprint(ply, cmd)

    if 
    ( cmd:KeyDown( IN_BACK ) )
    or
    ( ( cmd:KeyDown( IN_MOVERIGHT ) or cmd:KeyDown( IN_MOVELEFT ) ) and !cmd:KeyDown( IN_FORWARD ) )
    or
    ( CurTime() - ply:GetNWFloat( "LastLandingTime" ) < ply:GetNWFloat( "LandingStunDrag" ) )
    or
    ply:WaterLevel() >= 1
    then

        RemoveKeys( cmd, IN_SPEED )

    end

end

local function GetLandingDrag( velocity, ply, dt )

        local landingDragStrength = landingDragCVar:GetFloat()
        local landingDragTime = ply:GetNWFloat( "LandingStunDrag" )
        local landDrag = 1

        if CurTime() - ply:GetNWFloat( "LastLandingTime" ) > landingDragTime then

            if SERVER then

                ply:SetNWFloat( "LandingStunDrag", 0 )

            end

            else

                landDrag = 1 + dt * landingDragStrength

            end

        return landDrag

end

local function GetTrueFriction(velocity, coef, dt, moveDir)

    local speed = velocity:Length()
    local normal = -velocity:GetNormalized()
    local baseFriction = trueFrictionCVar:GetFloat() * coef
    
    if !moveDir:IsZero() then

        baseFriction = baseFriction * math.max( 0, normal:Dot( moveDir ) )

    else

        baseFriction = baseFriction * 2.5

    end

    local newSpd = math.max( 0, speed - baseFriction * dt )

    return newSpd / math.max( 1, speed )

end

--Code from the wiki, modified for mv and cmd
function RemoveKeys( mvorcmd, keys )

	-- Using bitwise operations to clear the key bits.
	local newbuttons = bit.band( mvorcmd:GetButtons(), bit.bnot( keys ) )
	mvorcmd:SetButtons( newbuttons )

end
--end

hook.Add("StartCommand", "ICStartCMD", function( ply, cmd )

    if IsPlayerExcluded(ply) then return end

    if sprintRestrictionsCVar:GetBool() and ply:GetMoveType() ~= MOVETYPE_NOCLIP then
            
        CanSprint( ply, cmd )

    end

end)

hook.Add("SetupMove", "ICSetupMove", function( ply, mv, cmd )
    
    if IsPlayerExcluded(ply) then return end
    
    if ply:GetMoveType() == MOVETYPE_WALK then

        local timeSinceLanding = CurTime() - ply:GetNWFloat( "LastLandingTime" )
        local nextPossibleJumpTime = ply:GetNWFloat( "LandingStunDrag" )

        if ( jumpDelayCVar:GetBool() and mv:KeyDown( IN_JUMP ) and timeSinceLanding < nextPossibleJumpTime and ply:GetMoveType() ~= MOVETYPE_LADDER ) then

		    RemoveKeys( mv, IN_JUMP )

	    end

    end

    if noJumpBoostCVar:GetBool() and mv:KeyDown( IN_JUMP ) and bit.band( mv:GetOldButtons(), IN_JUMP ) ~= IN_JUMP then

        mv:SetMaxClientSpeed( mv:GetVelocity():Length() / 1.5 )

    end

end)

hook.Add("Move", "ICMoveHook", function(ply, mv)

    if IsPlayerExcluded(ply) then return end

    if ply:OnGround() or ply:WaterLevel() >= 2 then

        local dt = FrameTime()

        if changeMoveSpeedCVar:GetBool() and math.abs(CurTime() - ply:GetNW2Float( "LastSpeedUpdate" )) > 0.3 then

            SetPlyMoveSpeeds( ply, mv )
            ply:SetNW2Float( "LastSpeedUpdate", CurTime() )

        end
        
        local waterLevel = ply:WaterLevel()
        local inwater = waterLevel >= 2


        if !inwater then
            
            local vel = mv:GetVelocity()

            local moveDir = ( mv:GetAngles():Forward() * mv:GetForwardSpeed() + mv:GetAngles():Right() * mv:GetSideSpeed() )
            moveDir.z = 0
            moveDir:Normalize()

            local frictionCoef = ply:GetFriction()
            vel = vel * GetTrueFriction( vel, frictionCoef, dt, moveDir ) / GetLandingDrag( vel, ply, dt )

            mv:SetVelocity( vel )

        end
        
    end

end)

hook.Add("OnPlayerHitGround", "ICHitGround", function(ply, inwater, onfloater, speed)
    
        if IsPlayerExcluded(ply) then return end
    
        ply:SetNWFloat( "LastLandingTime", CurTime() )
        local stunTime = math.Clamp( math.Remap( speed, 200, 650, 0.3, 1 ), 0.3, 1 )

        if stunTime > ply:GetNWFloat( "LandingStunDrag" ) then

            ply:SetNWFloat( "LandingStunDrag", stunTime )

        end

end)

local function ConsoleCommands()
    if airStrafeLimitsCVar:GetBool() then

        RunConsoleCommand( "sv_airaccelerate", 0.2 )

    end

    RunConsoleCommand( "sv_accelerate", 8 / inertiaCVar:GetFloat() )
    RunConsoleCommand( "sv_friction", 8 / inertiaCVar:GetFloat() )
    RunConsoleCommand( "sv_stopspeed", 0 )
    
end

ConsoleCommands()

cvars.AddChangeCallback( "IC_move_basic_inertia", function()

    RunConsoleCommand( "sv_accelerate", 8 / inertiaCVar:GetFloat() )
    RunConsoleCommand( "sv_friction", 8 / inertiaCVar:GetFloat() )

end )

cvars.AddChangeCallback( "IC_move_limits_air_strafing", function()

    RunConsoleCommand( "sv_airaccelerate", ( airStrafeLimitsCVar:GetBool() and 0.2 ) or ( 8 / inertiaCVar:GetFloat() ) )

end )

if CLIENT then
    
    hook.Add("PopulateToolMenu", "IC_PopulateToolMenu", function()
        spawnmenu.AddToolMenuOption( "Utilities", "Admin", "IC_OptionsMenu", "Inertia Controller", "", "", function( panel )

        panel:Help( "Basic" )
        
        panel:NumSlider( "Inertia Scale", "IC_move_basic_inertia", 0.1, 5, 3 )

        panel:NumSlider( "Friction", "IC_move_basic_friction", 0, 250, 3 )

        panel:Help( "Speed" )

        panel:CheckBox( "Change movement speed", "IC_move_speeds_changespeed" )

        panel:NumSlider( "Walk speed", "IC_move_speeds_walkspeed", 1, 400, 0)

        panel:NumSlider( "Sprint speed", "IC_move_speeds_sprintspeed", 1, 400, 0)

        panel:NumSlider( "Slow walk speed", "IC_move_speeds_slowwalkspeed", 1, 400, 0)

        panel:NumSlider( "Crouch walk speed", "IC_move_speeds_crouchedspeed", 1, 400, 0)

        panel:NumSlider( "Ladder climb speed", "IC_move_speeds_ladderspeed", 1, 400, 0)

        panel:NumSlider( "Crouch transition speed", "IC_move_speeds_duckspeed", 0, 1, 3)
        
        panel:NumSlider( "Jump height", "IC_move_speeds_jump_height", 0, 100, 0)

        panel:Help( "Limits" )

        panel:CheckBox( "Limit air strafing", "IC_move_limits_air_strafing" )

        panel:CheckBox( "Jump cooldown", "IC_move_limits_jump_cooldown" )

        panel:CheckBox( "Sprint restrictions", "IC_move_limits_sprint_restrictions" )

        panel:CheckBox( "No jump boost", "IC_move_limits_jump_boost" )

        panel:NumSlider( "Extra landing drag", "IC_move_limits_landing_drag", 0, 15, 3)

        end)
    end)

end
