local me = LocalPlayer()

// Settings 
local settings = {
    combat = {
        Silent = false,
        FixMovement = true,
        KeepSprint = true,
        FastWalk = true,
    },
    render = {
        Players = true,

        Names = true,
        Weapon = true,

        CrossHair = true,

        Minimap = false,
    },
    misc = {
        Gatherer = false,
    },
    entity = {
        Enabled = false,
        Trees = false,
    },
    exploit = {
        Lagger = false,
        Knocker = false,
    }

} 

// Render data 
local fonts = {}

fonts.render = { font = "Verdana", size = 12, weight = 100, antialias = false, outline = true }
fonts.menu = { font = "Verdana", size = 13, weight = 100, antialias = false }

for key, val in pairs( fonts ) do
    local fkey = string.format( "font.%s", key )
    surface.CreateFont( fkey, val )
    fonts[ key ] = fkey 
end

// Input handler 
local mouse = { x = 0, y = 0, down = false, click = false }

function mouse.hover( x, y, w, h )
    w = x + w 
    h = y + h 

    return ( mouse.x >= x and mouse.x <= w ) and ( mouse.y >= y and mouse.y <= h )
end 

// Interface 
local interface = {}

interface.frame = { x = 0, y = 0, w = 500, h = 300, visible = false }
interface.focus = false
interface.tab = "combat"

interface.typecase = {
    ["boolean"] = function( key, val, x, y ) 
        surface.SetDrawColor( 48, 48, 48 )
        surface.DrawRect( x + 3, y + 3, 10, 10 )

        surface.SetDrawColor( 64, 64, 64 )
        surface.DrawOutlinedRect( x + 3, y + 3, 10, 10 ) 

        if val then
            surface.SetTextPos( x + 2, y )
            surface.DrawText( "✓" )
        end

        surface.SetTextPos( x + 20, y )
        surface.DrawText( key )

        if mouse.hover( x, y, 16, 16 ) and mouse.click then
            local t = settings[ interface.tab ]
            t[ key ] = not t[ key ]
        end
    end
}

local holding = false
function interface.render()
    local frame = interface.frame

    local isdown = input.IsKeyDown( KEY_DELETE ) 
    if isdown and not holding then
        frame.visible = not frame.visible
        gui.EnableScreenClicker( frame.visible )
    end

    holding = isdown

    if not frame.visible then
        return 
    end

    local ismousedown = input.IsMouseDown( MOUSE_LEFT )
    mouse.click = ismousedown and not mouse.down
    mouse.down = ismousedown

    local mx, my = input.GetCursorPos()
    local x, y, w, h = frame.x, frame.y, frame.w, frame.h 

    -- render main frame 
    surface.SetDrawColor( 16, 16, 16 )
    surface.DrawRect( x, y, w, h  )

    surface.SetDrawColor( 48, 48, 48 )
    surface.DrawRect( x, y, w, 20 )

    surface.SetDrawColor( 64, 64, 64 )
    surface.DrawOutlinedRect( x, y, w, h ) 
    surface.DrawRect( x, y + 20, w, 1 ) 

    surface.SetTextPos( x + 6, y + 3 )
    surface.DrawText( "grustfunny.lua" )

    local tX = x + w
    for key, val in pairs( settings ) do
        local strlen, strh = surface.GetTextSize( key )
        tX = tX - 2 - ( strlen + 16 )

        surface.SetDrawColor( 64, 64, 64 )
        surface.DrawOutlinedRect( tX, y + 2, strlen + 16, 19 ) 

        surface.SetTextPos( tX + strlen / 4, y + 3 )
        surface.DrawText( key )

        if mouse.hover( tX, y + 2, strlen + 16, 19 ) and mouse.click then
            interface.tab = key 
        end

        if key == interface.tab then
            local pX, pY = x + 6, y + 26

            for vKey, vVal in pairs( settings[ key ] ) do
                local vType = type( vVal )
                
                if not interface.typecase[ vType ] then
                    print( string.format( "Unsupported type: %s !", vType ) )
                    continue 
                end

                interface.typecase[ vType ]( vKey, vVal, pX, pY )

                pY = pY + 20

                if pY >= ( y + h ) then
                    pY = y + 26
                    pX = pX + 100
                end
            end
        end
    end

    -- update input data
    if mouse.hover( x, y, w, h ) and mouse.down and not interface.focus then
        frame.x = frame.x + ( mx - mouse.x )
        frame.y = frame.y + ( my - mouse.y )
    end 

    mouse.x, mouse.y = mx, my 
end

// Hook manager 
local color_white = color_white

local hk = {}
local resourcetbl = {}
local playerData = {}

local scrw, scrh = ScrW(), ScrH()
local scrwc, scrhc = math.floor( scrw / 2 ), math.floor( scrh / 2 )

local allowed = { ["rust_ore"] = true, ["rust_hemp"] = true, ["prop_dynamic"] = true, ["rust_sleepingplayer"] = true }
local classtoname =  { ["rust_ore"] = "Ore - %s", ["rust_hemp"] = "Hemp", ["prop_dynamic"] = "Tree", ["rust_sleepingplayer"] = "Sleepy" }

local orenames = {
    [0] = "Stone",
    [1] = "Metall",
    [2] = "Sulfur",
}

function hk.PreViewRender()
    surface.SetAlphaMultiplier( 255 ) 
    surface.SetFont( fonts.render )

    -- render visuals 
    if settings.render.Players then
        local plys = player.GetAll()

        local frametime = FrameTime() * 128

        for i = 1, #plys do
            local ply = plys[ i ]

            if not playerData[ ply ] then
                playerData[ ply ] = {}
            end

            if ply == me then 
                continue
            end

            if not ply:Alive() then 
                continue 
            end

            -- Collision bounds
            local pos = ply:GetPos()

            local mins = ply:OBBMins()
            local maxs = ply:OBBMaxs()

            local vec2 = ( pos + maxs ):ToScreen()
            local vx, vy = vec2.x, vec2.y 

            if not vec2.visible then
                continue 
            end

            -- Dormant fade
            local data = playerData[ ply ]
            local dormant = ply:IsDormant()

            local target = dormant and 32 or 255
            data.fade = math.Approach( data.fade or 255, target, frametime )

            surface.SetTextColor( 255, 255, 255, data.fade )

            local pad = 0

            -- Name 
            if settings.render.Names then 
                surface.SetTextPos( vx, vy + pad )
                surface.DrawText( ply:Name() )

                pad = pad + 10
            end 

            -- Weapon
            local weapon = ply:GetActiveWeapon()
            if settings.render.Weapon and IsValid( weapon ) then 
                surface.SetTextPos( vx, vy + pad )
                surface.DrawText( weapon:GetClass() )

                pad = pad + 10
            end

        end
    end

    -- show resources
    if #resourcetbl != 0 and settings.misc.Gatherer then
        cam.Start3D()

        for i = 1, #resourcetbl do
            local ent = resourcetbl[ i ]

            if not IsValid( ent ) then
                continue 
            end

            render.DrawWireframeBox( ent:GetPos(), ent:GetAngles(), ent:OBBMins(), ent:OBBMaxs(), color_white, true )
        end
    
        cam.End3D()
    end

    surface.SetTextColor( 255, 128, 64 )

    -- entity render 
    local entities = ents.GetAll()
    local mypos = me:GetPos()

    if settings.entity.Enabled then
        for i = 1, #entities do
            local ent = entities[ i ]
            local class = ent:GetClass()

            if ent.Containers then
                local pos = ent:GetPos():ToScreen()
                local str = classtoname[ class ]

                surface.SetTextPos( pos.x, pos.y + 10 )
                surface.DrawText( "Container" )
            end

            if not allowed[ class ] or ( ent:GetPos() ):DistToSqr( mypos ) > 16777216 then
                continue 
            end

            if ( not settings.entity.Trees or not ent:IsTree() ) and class == "prop_dynamic" then
                continue 
            end
                
            local pos = ent:GetPos():ToScreen()
            local str = classtoname[ class ]

            surface.SetTextPos( pos.x, pos.y )
            surface.DrawText( class == "rust_ore" and string.format( str, orenames[ ent:GetSkin() ] or "Unlisted" ) or str )
        end
    end

    surface.SetTextColor( 255, 255, 255 )

    -- render xhair 
    if settings.render.CrossHair then
        render.OverrideBlend( true, 3, 1, 1 )

        surface.SetDrawColor( 255, 255, 255 )

        surface.DrawRect( scrwc - 7, scrhc - 1, 15, 3 )
        surface.DrawRect( scrwc - 1, scrhc - 7, 3, 15 )

        render.OverrideBlend( false )
    end

    -- render interface 
    surface.SetFont( fonts.menu )

    interface.render()
end

local lastresupdate = 0

local function FixMovement( cmd, yaw )
    local viewAngles = cmd:GetViewAngles()

	local pitch = math.NormalizeAngle( viewAngles.x )
	local inverted = ( pitch > 89 || pitch < -89 ) and 1 or -1
	local angleDiff = math.rad( math.NormalizeAngle( ( viewAngles.y - yaw ) * inverted ) )
    local OldForward, OldSide = cmd:GetForwardMove(), cmd:GetSideMove()

    cmd:SetForwardMove( OldForward * -math.cos( angleDiff ) * inverted + OldSide * math.sin( angleDiff ) )
    cmd:SetSideMove( OldForward * math.sin( angleDiff ) * inverted + OldSide * math.cos( angleDiff )  )
end

local directionalMove = { IN_BACK, IN_MOVERIGHT, IN_MOVELEFT }
 
function hk.CreateMove( cmd )
    local number = cmd:CommandNumber()

    if number == 0 then
        return 
    end

    local curtime = CurTime()
    local mypos = me:GetPos()

    if settings.combat.FixMovement then
        for i = 1, #directionalMove do
            cmd:RemoveKey( directionalMove[ i ] )
        end
    end

    if settings.combat.KeepSprint then
        cmd:AddKey( IN_SPEED )
    end

    if settings.combat.FastWalk and me:IsFlagSet( FL_ONGROUND ) and math.abs( cmd:GetSideMove() ) < 1 and math.abs( cmd:GetForwardMove() ) >= 1 then
        local moveType = me:GetMoveType()
        local waterLevel = me:WaterLevel()

        if moveType != MOVETYPE_NOCLIP and moveType != MOVETYPE_LADDER and waterLevel <= 1 then
            cmd:SetSideMove(cmd:CommandNumber() % 2 == 0 and -5250 or 5250)
        end
    end
    
    if settings.misc.Gatherer then
        if lastresupdate < curtime then
            local entities = ents.GetAll()
            for i = 1, #entities do
                local ent = entities[ i ]
    
                if not ent:IsTree() then
                    continue 
                end

                local pos = ent:GetPos()
                    
                if pos:Distance2DSqr( mypos ) > 1048576 then -- 1024 units
                    continue 
                end
    
                resourcetbl[ #resourcetbl + 1 ] = ent 
            end

            lastresupdate = curtime + 5
        end

        if #resourcetbl == 0 then 
            return
        end

        local gBest = false 
        local gMax = 1048576
        
        for i = 1, #resourcetbl do
            local ent = resourcetbl[ i ]

            if not IsValid( ent ) then
                table.remove( resourcetbl, i )
                continue 
            end

            local pos = ent:GetPos()
                    
            local dist = pos:Distance2DSqr( mypos )
            if dist < gMax then
                gBest = ent 
                gMax = dist
            end
        end

        local gPos = gBest:GetPos()
        local gDist = gPos:Distance2DSqr( mypos )
        local dAng = ( gPos - me:EyePos() ):Angle()
        
        cmd:SetSideMove( 0 )
        cmd:SetForwardMove( 10000 )

        FixMovement( cmd, dAng.y )

        dAng.p = 0
        cmd:SetViewAngles( dAng )
    
        if number % 2 == 0 and gDist < 16384 then
            cmd:SetForwardMove( 0 )
            cmd:AddKey( IN_ATTACK )
        end
    end
end

local viewdata = { origin = EyePos(), angles = EyeAngles(), fov = 0 }
function hk.CalcView( ply, vec, ang, fov, zn, zf )
    viewdata.origin = vec
    viewdata.angles = ang 
    viewdata.fov = 90

    return viewdata
end

function hk.CalcViewModelView( wep, vm, op, oa, vec, ang )
    return viewdata.origin, viewdata.angles
end 

// Lagger 
local tickrate = math.floor( 1 / engine.TickInterval() ) - 1
local swap = false
local writtenTicks = 0 
local coolingTicks = 0 

function hk.Tick()
    if settings.exploit.Knocker then
        local ent = me:GetEyeTrace().Entity 

        if ent.Model then
            if IsValid( ent ) and ent.Model:find( "door" ) then
                for i = 1, 500 do
                    net.Start("gRust.Knock", true )
                        net.WriteEntity( ent )
                    net.SendToServer()
                end
            end
        end
    end

    if not settings.exploit.Lagger then
        return
    end

    if writtenTicks > tickrate then
        swap = true 

        writtenTicks = 0
    end

    coolingTicks = swap and coolingTicks + 1 or 0

    if coolingTicks > 16 then
        swap = false
    end

    if swap then
        return 
    end

    for i = 1, 99 do
        net.Start( "gRust.CreateTeam", true )
        net.SendToServer()

        net.Start( "gRust.LeaveTeam", true )
        net.SendToServer()
    end

    writtenTicks = writtenTicks + 1
    print( string.format( "Written %i", writtenTicks ) )
end 

for key, val in pairs( hk ) do
    hook.Add( key, string.format( "hk.%s", key ), val )
end