--[[

░██████╗███████╗███╗░░██╗████████╗██╗███╗░░██╗███████╗██╗░░░░░
██╔════╝██╔════╝████╗░██║╚══██╔══╝██║████╗░██║██╔════╝██║░░░░░
╚█████╗░█████╗░░██╔██╗██║░░░██║░░░██║██╔██╗██║█████╗░░██║░░░░░
░╚═══██╗██╔══╝░░██║╚████║░░░██║░░░██║██║╚████║██╔══╝░░██║░░░░░
██████╔╝███████╗██║░╚███║░░░██║░░░██║██║░╚███║███████╗███████╗
╚═════╝░╚══════╝╚═╝░░╚══╝░░░╚═╝░░░╚═╝╚═╝░░╚══╝╚══════╝╚══════╝

████████╗██╗░░██╗███████╗  ██████╗░███████╗░██████╗████████╗
╚══██╔══╝██║░░██║██╔════╝  ██╔══██╗██╔════╝██╔════╝╚══██╔══╝
░░░██║░░░███████║█████╗░░  ██████╦╝█████╗░░╚█████╗░░░░██║░░░
░░░██║░░░██╔══██║██╔══╝░░  ██╔══██╗██╔══╝░░░╚═══██╗░░░██║░░░
░░░██║░░░██║░░██║███████╗  ██████╦╝███████╗██████╔╝░░░██║░░░
░░░╚═╝░░░╚═╝░░╚═╝╚══════╝  ╚═════╝░╚══════╝╚═════╝░░░░╚═╝░░░

░█████╗░███╗░░██╗████████╗██╗░░░░░░░█████╗░██████╗░░█████╗░░██████╗██╗░░██╗
██╔══██╗████╗░██║╚══██╔══╝██║░░░░░░██╔══██╗██╔══██╗██╔══██╗██╔════╝██║░░██║
███████║██╔██╗██║░░░██║░░░██║█████╗██║░░╚═╝██████╔╝███████║╚█████╗░███████║
██╔══██║██║╚████║░░░██║░░░██║╚════╝██║░░██╗██╔══██╗██╔══██║░╚═══██╗██╔══██║
██║░░██║██║░╚███║░░░██║░░░██║░░░░░░╚█████╔╝██║░░██║██║░░██║██████╔╝██║░░██║
╚═╝░░╚═╝╚═╝░░╚══╝░░░╚═╝░░░╚═╝░░░░░░░╚════╝░╚═╝░░╚═╝╚═╝░░╚═╝╚═════╝░╚═╝░░╚═╝

█▀█ █▀█ █▀█ █░█ █ █▀▄ █▀▀ █▀▄   █▄▄ █▄█   █░░ █ █▄▄ █▄▄ █▄█ ▀ █▀   █▀ ▄▀█ █▄░█ █▀▄ █▄▄ █▀█ ▀▄▀
█▀▀ █▀▄ █▄█ ▀▄▀ █ █▄▀ ██▄ █▄▀   █▄█ ░█░   █▄▄ █ █▄█ █▄█ ░█░ ░ ▄█   ▄█ █▀█ █░▀█ █▄▀ █▄█ █▄█ █░█

█▀▄▀█ ▄▀█ █▀█ █▀▀ █░█   █▄▄   ▀█ █▀█ ▀█ ▀█   █▀ █▀▀ █▀█ ▄▀█ █▀█ █▀█ █▀▀ █▀▄   █░█ █▀▀ █▀█ █▀ █ █▀█ █▄░█
█░▀░█ █▀█ █▀▄ █▄▄ █▀█   █▄█   █▄ █▄█ █▄ █▄   ▄█ █▄▄ █▀▄ █▀█ █▀▀ █▀▀ ██▄ █▄▀   ▀▄▀ ██▄ █▀▄ ▄█ █ █▄█ █░▀█

]]--

--[[
this is sentinel, an anti-crash lag prevention system for Libby's Sandbox
also used on rose's server

entity ghosting, lag prevention when major lag detected, and crash prevention for tools
[NOT A PROP PROTECTION SYSTEM]
]]--


disablephysics = 0
silentabortpropspawning = 0
sentinellagpercent = 0
lagwarnings = 0
abnormallagactivity = 0
abnormallagactivity2 = 0
nocrashman = 0
spawnedentityuser = 0

local SentinelLagrange = CreateConVar("sentinel_SentinelLagDet_range", "0.01", {FCVAR_SERVER_CAN_EXECUTE})

local SentinelLagcount = CreateConVar("sentinel_SentinelLagDet_count", "500000", {FCVAR_SERVER_CAN_EXECUTE})

local SentinelLagquiet = CreateConVar("sentinel_SentinelLagDet_quiet", "150000", {FCVAR_SERVER_CAN_EXECUTE})

local SentinelLagverbose = CreateConVar("sentinel_SentinelLagDet_verbose", "0", {FCVAR_SERVER_CAN_EXECUTE})

local SentinelLagexecute = CreateConVar("sentinel_SentinelLagDet_execute", "fuckoff", {FCVAR_SERVER_CAN_EXECUTE})

--local SentinelLagexecute = CreateConVar( "sentinel_SentinelLagDet_execute", "fuckoff", { FCVAR_SERVER_CAN_EXECUTE } )
local SentinelLagcount_meltdown = CreateConVar("SentinelLagcount_meltdown", "100000", {FCVAR_SERVER_CAN_EXECUTE})

local SentinelLagexecute_meltdown = CreateConVar("SentinelLagexecute_meltdown", "fuckoff2", {FCVAR_SERVER_CAN_EXECUTE})

--local SentinelLagexecute_meltdown = CreateConVar( "SentinelLagexecute_meltdown", "ulx maprestart", { FCVAR_SERVER_CAN_EXECUTE } )
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
function libre_freezeall()
    local ent = ents.FindByClass("*")

    for _, ent in pairs(ent) do
        if ent:IsValid() then
            --entityfound = entityfound + 1
            local phys = ent:GetPhysicsObject()

            if IsValid(phys) then
                ----print("[Sentinel]: Yo this entity has physics!")
                if not ent:IsPlayer() then
                    --entityfroze = entityfroze + 1                             
                    phys:EnableMotion(false)
                end

                if not ent:IsPlayer() then end -- only needed when i need it --ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE)
                if ent:IsPlayer() then end ----print("this entity is a player")
            else
            end
            ----print("[Sentinel]: This dumbshit entity has no physics!")
        end
    end
end

function libre_sleepthemall()
    local ent = ents.FindByClass("*")

    for _, ent in pairs(ent) do
        if ent:IsValid() then
            local phys = ent:GetPhysicsObject()

            if ent:IsValid() then
                if phys:IsValid() then
                    if phys:IsMotionEnabled() == true then
                        if ent:IsValid() then
                            if ent:GetVelocity():Length() < 0.01 then
                                phys:Sleep()
                            end
                        end
                    end
                end
            end
        end
    end
end

function libre_checkents_lagactivity(ent)
    local ent = ents.FindByClass("prop_physics")

    for _, ent in pairs(ent) do
        local pos = ent:GetPos()
        local propsnear = ents.FindInSphere(pos, 0.25)
        local stackent = 0

        for k, v in pairs(propsnear) do
            local whatisthatent = ent:GetClass()
            local whatisthatmodel = ent:GetModel()

            if whatisthatent == "prop_physics" then
                stackent = stackent + 1

                if stackent > 16 then
                    PrintMessage(HUD_PRINTCONSOLE, "[Sentinel]: " .. ent:GetModel() .. " was removed due to being a suspicious entity. (lag detected)")
                    ent:Remove()
                    stackent = 0
                    --timer.Create("libre_warnsuspiciousentities", 0.6, 1, function() PrintMessage(HUD_PRINTTALK, "[libre]: entity: " .. whatisthatent .. " model: " .. whatisthatmodel .. "") end)
                end

                -- if lolno > 999999999999999999999999999999999999999999999999999999999999999 then
                --     -- We set all props collision to not collide with each other. duh
                --     lolno = 0
                --     RunConsoleCommand("ulx", "notifications", "*", "[libby_lag_prevention]: Thanks, (anti_stacker_preventions_executed)", "generic", "6")

                --     timer.Create("libre_bruhhetryingtocrashserver", 0, 1000, function()
                --         local Ent = ents.FindByClass("prop_physics")

                --         for _, Ent in pairs(Ent) do
                --             if Ent:IsValid() then
                --                 Ent:SetCollisionGroup(COLLISION_GROUP_WORLD)
                --             end
                --         end
                --     end)
                -- end
            end
        end
        --end
    end
end

--RunConsoleCommand("sentinel_SentinelLagDet_range","0.0025")
-- 0.0016 or 0.0018 or 0.0019 or 0.0020
RunConsoleCommand("sentinel_SentinelLagDet_range", "0.01") -- or 0.01 or 0.0044 or 0.02
RunConsoleCommand("sentinel_SentinelLagDet_count", "999999")
RunConsoleCommand("sentinel_SentinelLagDet_quiet", "999999")

--RunConsoleCommand("sentinel_SentinelLagDet_meltdown","999999")
timer.Create("sentinel_fjdagofd", 0.025, 0, function()
    if lagwarnings < -0.01 then
        lagwarnings = 0
    end

    lagwarnings = lagwarnings - 1
end)

local currdif1 = 9999
local currdif2 = 0
local sentinelcurrcount = 0
local sentinelclearTime = 0
local sentinelframecount = 0

hook.Add("Think", "sentinel_SentinelLagDetThink", function()
    sentinelframecount = sentinelframecount + 1
end)

-- Calculate SentinelLag by comparing SysTime to CurTime.  
-- Increased differential indicates potential SentinelLag
local function GetCurrentDelta()
    --local frametime = FrameTime()
    --fps = 1 / frametime
    local SysCurrDiff = SysTime() - CurTime() -- current differential
    currdif2 = math.Round(SysCurrDiff - currdif1, 6) -- change in differential since last check
    currdif1 = SysCurrDiff

    return currdif2
end

local function SentinelLagMonThreshold()
    -- i haz SentinelLag?
    if sentinelcurrcount > 0 then
        if RealTime() > sentinelclearTime then end -- passed our clear time?
    end

    local delt = GetCurrentDelta()

    if SentinelLagverbose:GetInt() == 1 or delt >= SentinelLagrange:GetFloat() then
        if sentinelcurrcount == SentinelLagcount_meltdown:GetInt() then end
    end

    if delt < SentinelLagrange:GetFloat() then return false end
    sentinellagpercent = sentinellagpercent + 2
    abnormallagactivity2 = abnormallagactivity2 + 1

    timer.Create("sentinel_okayresetlagpercent", 0.25, 1, function()
        abnormallagactivity2 = 0
    end)

    lagwarnings = lagwarnings + 5
    nocrashman = nocrashman + 1

    timer.Create("nocrashserver_", 0.25, 1, function()
        nocrashman = 0
    end)

    if lagwarnings > 19.99 then
        disablephysics = 1

        timer.Create("enableback_sentinel_physics", 0.5, 1, function()
            disablephysics = 0
        end)

        --libre_checkents_lagactivity()
        --libre_sleepthemall()
        --PrintMessage( HUD_PRINTCONSOLE, "[SentinelLagCount]: !!! The server seem to have lagged (I am worried and disabled physics for 0.5 seconds) !!!")
        lagwarnings = 0
        abnormallagactivity = abnormallagactivity + 1

        timer.Create("sentinel_noabnormallagactivity", 2, 1, function()
            abnormallagactivity = 0
        end)
    end

    if abnormallagactivity > 10.1 then
        PrintMessage(HUD_PRINTCONSOLE, "[Sentinel] (stage 1) lag prevention payload, start!")

        function libre_freezeall()
            local ent = ents.FindByClass("*")

            for _, ent in pairs(ent) do
                if ent:IsValid() then
                    --entityfound = entityfound + 1
                    local phys = ent:GetPhysicsObject()

                    if IsValid(phys) then
                        ----print("[Sentinel]: Yo this entity has physics!")
                        if not ent:IsPlayer() then
                            phys:EnableMotion(false)
                        end

                        if not ent:IsPlayer() then
                            ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE)
                        end
                    else
                    end
                    ----print("[Sentinel]: This dumbshit entity has no physics!")
                end
            end
        end

        function libre_checkents(ent)
            local ent = ents.FindByClass("prop_physics")

            for _, ent in pairs(ent) do
                local pos = ent:GetPos()
                local propsnear = ents.FindInSphere(pos, 0.25)
                local stackent = 0

                for k, v in pairs(propsnear) do
                    local whatisthatent = ent:GetClass()
                    local whatisthatmodel = ent:GetModel()

                    if whatisthatent == "prop_physics" then
                        stackent = stackent + 1

                        if stackent > 18 then
                            PrintMessage(HUD_PRINTCONSOLE, "[Sentinel]: " .. ent:GetModel() .. " removed :) (sentinel lag detection)")
                            ent:Remove()
                            stackent = 0
                            --timer.Create("libre_warnsuspiciousentities", 0.6, 1, function() PrintMessage(HUD_PRINTTALK, "[libre]: entity: " .. whatisthatent .. " model: " .. whatisthatmodel .. "") end)
                        end

                        -- if lolno > 999999999999999999999999999999999999999999999999999999999999999 then
                        --     -- We set all props collision to not collide with each other. duh
                        --     lolno = 0
                        --     RunConsoleCommand("ulx", "notifications", "*", "[libby_lag_prevention]: Thanks, (anti_stacker_preventions_executed)", "generic", "6")

                        --     timer.Create("libre_bruhhetryingtocrashserver", 0, 1000, function()
                        --         local Ent = ents.FindByClass("prop_physics")

                        --         for _, Ent in pairs(Ent) do
                        --             if Ent:IsValid() then
                        --                 Ent:SetCollisionGroup(COLLISION_GROUP_WORLD)
                        --             end
                        --         end
                        --     end)
                        -- end
                    end
                end
                --end
            end
        end

        libre_checkents()
        libre_freezeall()
        -- As fast as we can, must freeze every possible entity on the server and check entities
        -- Allow spawning props to be disabled, to prevent further damage to our server network!
        propdisabled = 1

        timer.Create("sentinel_allowpropspawningman", 6, 1, function()
            propdisabled = 0
        end)

        -- It's also the time to start disabling physics for a short time to prevent even more further damage to our server network
        nophysics = 1
        disablephysics = 1

        timer.Create("sentinel_nophysics_start", 1, 4, function()
            nophysics = 1
            disablephysics = 1
        end)

        timer.Create("sentinel_turnbackphysicsonrightnow", 5, 1, function()
            nophysics = 0
            disablephysics = 0
        end)

        -- No duplicators allowed at this time, There is some serious lag going on!
        denieddupe = 1

        timer.Create("denieddupe_reset", 6, 1, function()
            denieddupe = 0
        end)

        -- We would also like to disable collisions on every single prop to further minimize the risk of potential server crashing by collision
        local ent = ents.FindByClass("prop_physics")

        for _, ent in pairs(ent) do
            if ent:IsValid() then
                ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
            end
        end

        PrintMessage(HUD_PRINTCONSOLE, "[Sentinel] (stage 2) attempted to prevent further server lag")
        sleepphysics = 0
        physicsprop = 0
        frozenphysics = 0
        -- Notify peasants lag prevention payload has executed
        sentinel.Broadcast("⚠ suspicious lag activity, payload executed ⚠")
        cleanupcount = cleanupcount + 1

        timer.Create("sentinel_resetcleanupcount", 5, 1, function()
            cleanupcount = 0
        end)

        -- Notify peasants lag prevention payload has executed (along with an auto-cleanup system)
        if cleanupcount > 10 then
            RunConsoleCommand("gmod_admin_cleanup")
            sentinel.Broadcast("⚠ Excessive lag, Cleaning up map! ⚠")
        end

        PrintMessage(HUD_PRINTCONSOLE, "[Sentinel] (stage 3) lag prevention notifications")

        -- Freeze and check everything before allowing physics again :)
        timer.Create("sentinel_checkbeforeallowphysics", 7, 1, function()
            PrintMessage(HUD_PRINTCONSOLE, "[Sentinel] stage (4) freezing and checking entities before allowing physics")
            libre_checkents()
            libre_freezeall()
        end)

        -- freeze people who used duplicator in the past :/
        if not duplicatorusernick == 0 then
            if IsValid(player.GetBySteamID(duplicatorusernick)) then
                if sentinel.PermissionCheck(ply) then
                    sentinel.Broadcast(ply:Nick() .. " was spared from being frozen due to having the permission :(")

                    return
                end

--[[
                ply:Lock()
                sentinel.Broadcast(ply:Nick() .. " got frozen for using the duplicator tool")
                net.Start("sentinel_notif")
                net.WriteString("Sentinel froze you to make sure you aren't causing lag!")
                net.WriteInt(NOTIFY_ERROR, 5)
                net.WriteInt(8, 5)
                net.Send(ply)
                net.Start("sentinel_frozen")
                net.Send(ply)
]]--
            end

            -- Unfreeze them
            timer.Create("sentinel_unfreezebroo", 10, 1, function()
                RunConsoleCommand("ulx", "unfreeze", "*")
            end)
        end

        --[[
-- wow, even too many explosions can cause lag! delete all env_explosion
libre_haltexplosions()
libre_haltexplosions2()
libre_haltexplosions3()
libre_haltexplosions4()
]]
        --
        abnormallagactivity = 0
    end

    sentinelclearTime = RealTime() + SentinelLagquiet:GetInt() -- bump our clear time
    if sentinelcurrcount == SentinelLagcount:GetInt() then return true end -- we've hit our alert threshold. 

    return false
end

-- really shouldn't change the periodicity!
// 0 or engine.TickInterval()
timer.Create("sentinel_SentinelLagDetCheckPerf", engine.TickInterval(), 0, function()
    if SentinelLagMonThreshold() then
        --ServerLog("[sentinel_SentinelLagDetECTOR] SentinelLag detected!\n")
        if SentinelLagexecute:GetString() ~= "" then end
    end

    sentinelframecount = 0
end)

timer.Create("lowerthesentinellagpercent", 0.025, 0, function()
    sentinellagpercent = sentinellagpercent - 1

    if sentinellagpercent < 1 then
        sentinellagpercent = sentinellagpercent + 2
    end
end)

-- Anti-crash module moment
timer.Create("thefuckistheserverlaggingorwhat", 0.01, 0, function()
    if disablephysics == 1 then
        //RunConsoleCommand("phys_timescale", "1")
    end

    if sentinellagpercent > 22 and sentinellagpercent < 40 then

        timer.Create("resettimescale", 0.5, 1, function()
        end)
    end

    if sentinellagpercent > 40 and sentinellagpercent < 80 then

        timer.Create("resettimescale", 0.5, 1, function()
        end)
    end

end)

timer.Create("sentinel_lagpercentcheck", 1, 1, function()
    if sentinellagpercent < 2 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: | status: none")
    if sentinellagpercent > 1 and sentinellagpercent < 10 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: | status: fine")
    if sentinellagpercent > 10 and sentinellagpercent < 20 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: || status: very minimal")
    if sentinellagpercent > 20 and sentinellagpercent < 30 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: ||| status: minimal")
    if sentinellagpercent > 30 and sentinellagpercent < 40 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: |||| status: medium")
    if sentinellagpercent > 40 and sentinellagpercent < 50 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: ||||| status: medium")
    if sentinellagpercent > 50 and sentinellagpercent < 60 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: |||||| status: high") --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: |||||| status: high")
    if sentinellagpercent > 60 and sentinellagpercent < 70 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: ||||||| status: pretty high") --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: ||||||| status: pretty high")
    if sentinellagpercent > 70 and sentinellagpercent < 80 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: |||||||| (critical) status: very high") --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: |||||||| (critical) status: very high")
    if sentinellagpercent > 80 and sentinellagpercent < 90 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: ||||||||| (critical) status: very high") --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: ||||||||| (critical) status: very high")
    if sentinellagpercent > 90 and sentinellagpercent < 100 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: |||||||||| (very critical) status: suffering") --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  bar: |||||||||| (very critical) status: suffering")
    if sentinellagpercent > 100 then end --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  (MAJOR LAG DETECTED) status: IMMEDIATE") --print("[Sentinel]: estimated lag percentage: " .. sentinellagpercent .. "%  (MAJOR LAG DETECTED) status: IMMEDIATE")

    if sentinellagpercent > 200 then
        sentinellagpercent = 50
    end

    if sentinellagpercent > 50 then
        --libre_checkents()
        -- abort prop spawning
        silentabortpropspawning = 1

        timer.Create("nowitistime_toallowpropspawn", 4, 1, function()
            silentabortpropspawning = 0
        end)
    end
end)

sentinel.Broadcast("physics_module loaded")