что общего у этого кода
script_name("CamHack")
script_author("sanek a.k.a Maks_Fender, edited by ANIKI")
require "lib.moonloader"
local mad = require "MoonAdditions"

-- ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ И КОНСТАНТЫ 
local camHack_posX, camHack_posY, camHack_posZ = 0, 0, 0 -- Координаты позиции камеры
local camHack_angZ, camHack_angY = 0, 0 -- Углы обзора камеры
local camHack_speed = 1.0 -- Скорость камеры

local camHack_flyModeActive = 0 -- Режим полета: 0 - отключен, 1 - включен
local camHack_radarHud = 0 -- Состояние радара и HUD: 0 - скрыто, 1 - показано
local camHack_keyPressed = 0 -- Состояние нажатой клавиши: 0 - не нажата, 1 - нажата

function main()
	while true do
		wait(0)
		
		if camHack_flyModeActive == 0 then
			checkFlyModeActivation()
		else
			handleFlyMode()
		end
	end
end

function checkFlyModeActivation()
	if isKeyDown(VK_C) and isKeyDown(VK_1) then
		enterFlyMode()
	end
end

function enterFlyMode()
	displayRadar(false)
	displayHud(false)
	camHack_posX, camHack_posY, camHack_posZ = getCharCoordinates(playerPed)
	camHack_angZ = -getCharHeading(playerPed) -- Inverted for correct orientation
	camHack_angY = 0.0
	setFixedCameraPosition(camHack_posX, camHack_posY, camHack_posZ, 0.0, 0.0, 0.0)
	lockPlayerControl(true)
	camHack_flyModeActive = 1
end

function handleFlyMode()
	if not sampIsChatInputActive() and not isSampfuncsConsoleActive() then
		adjustCameraAngles()
		moveCamera()
		drawDebugText()
		toggleRadarAndHud()
		adjustSpeed()
		checkFlyModeExit()
	end
end

function adjustCameraAngles()
	local offMouX, offMouY = getPcMouseMovement()
	camHack_angZ = (camHack_angZ + offMouX / 4.0) % 360
	camHack_angY = math.max(math.min(camHack_angY + offMouY / 4.0, 89.0), -89.0)
end

function moveCamera()
	local movX, movY, movZ = calculateMovementVectors()
	
	if isKeyDown(VK_W) then
		movePosition(movX, movY, movZ)
	elseif isKeyDown(VK_S) then
		movePosition(-movX, -movY, -movZ)
	end

	if isKeyDown(VK_A) or isKeyDown(VK_D) then
		sideMove(isKeyDown(VK_A) and -math.pi / 2 or math.pi / 2)
	end

	if isKeyDown(VK_SPACE) then
		camHack_posZ = camHack_posZ + camHack_speed
	elseif isKeyDown(VK_SHIFT) then
		camHack_posZ = camHack_posZ - camHack_speed
	end
	
	updateCamera()
end

function calculateMovementVectors()
	local radZ, radY = math.rad(camHack_angZ), math.rad(camHack_angY)
	local sinZ, cosZ, sinY, cosY = math.sin(radZ), math.cos(radZ), math.sin(radY), math.cos(radY)
	return cosY * sinZ, cosY * cosZ, sinY
end

function movePosition(movX, movY, movZ)
	camHack_posX = camHack_posX + movX * camHack_speed
	camHack_posY = camHack_posY + movY * camHack_speed
	camHack_posZ = camHack_posZ + movZ * camHack_speed
end

function sideMove(angleOffset)
	local sideZ = math.sin(math.rad(camHack_angZ) + angleOffset)
	local sideX = math.cos(math.rad(camHack_angZ) + angleOffset)
	camHack_posX = camHack_posX + sideZ * camHack_speed
	camHack_posY = camHack_posY + sideX * camHack_speed
end

function updateCamera()
	setFixedCameraPosition(camHack_posX, camHack_posY, camHack_posZ, 0.0, 0.0, 0.0)
	local movX, movY, movZ = calculateMovementVectors()
	pointCameraAtPoint(camHack_posX + movX, camHack_posY + movY, camHack_posZ + movZ, 2)
end

function toggleRadarAndHud()
	if camHack_keyPressed == 0 and isKeyDown(VK_F10) then
		camHack_keyPressed = 1
		camHack_radarHud = 1 - camHack_radarHud
		displayRadar(camHack_radarHud == 1)
		displayHud(camHack_radarHud == 1)
	end
	if wasKeyReleased(VK_F10) and camHack_keyPressed == 1 then camHack_keyPressed = 0 end
end

function adjustSpeed()
	if isKeyDown(187) then
		camHack_speed = camHack_speed + 0.01
	elseif isKeyDown(189) then
		camHack_speed = math.max(camHack_speed - 0.01, 0.01)
	end
	printStringNow(string.format("Speed: %.2f", camHack_speed), 1000)
end

function checkFlyModeExit()
	if isKeyDown(VK_C) and isKeyDown(VK_2) then
		exitFlyMode()
	end
end

function exitFlyMode()
	displayRadar(true)
	displayHud(true)
	lockPlayerControl(false)
	restoreCameraJumpcut()
	setCameraBehindPlayer()
	camHack_flyModeActive = 0
	camHack_radarHud = 0
end

function drawDebugText()
	local movX, movY, movZ = calculateMovementVectors()
	local text = string.format(
		"~p~~h~CamHack Debug Info :3~n~" ..
		"~p~~h~~h~Position:~n~" ..
		" X: ~p~~h~%.2f ~p~~h~~h~ Y: ~p~~h~%.2f ~p~~h~~h~ Z: ~p~~h~%.2f~n~" ..
		"~p~~h~~h~Direction:~n~" ..
		" X: ~p~~h~%.2f ~p~~h~~h~ Y: ~p~~h~%.2f ~p~~h~~h~ Z: ~p~~h~%.2f~n~" ..
		"~p~~h~~h~Angles:~n~" ..
		" Z: ~p~~h~%.2f ~p~~h~~h~degrees~n~" ..
		" Y: ~p~~h~%.2f ~p~~h~~h~degrees~n~" ..
		"~p~~h~~h~Speed:~n~" ..
		" Current: ~p~~h~%.2f ~p~~h~~h~units/sec~n~",
		camHack_posX, camHack_posY, camHack_posZ,
		camHack_posX + movX, camHack_posY + movY, camHack_posZ + movZ,
		camHack_angZ, camHack_angY, camHack_speed
	)
	mad.draw_text(text, 45, 240, mad.font_style.BEFORE_TEXT, 0.48, 1.12, mad.font_align.LEFT, 9999, true, false, 
	              255, 255, 255, 255, 1, 2, 0, 0, 0, 150)
end

local sampev = require "lib.samp.events"

function sampev.onPlayerChatBubble(id, col, dist, dur, msg)
	if camHack_flyModeActive == 1 then return {id, col, 1488, dur, msg} end
end

и у этого кода

local mad = require "MoonAdditions"
local socket = require "socket"

-- ================== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ И КОНСТАНТЫ ==================

-- константы меню
local MENU_TITLE_SIZE 		= 1.40
local MENU_ITEM_HEIGHT 		= 17
local MENU_ITEM_WIDTH 		= 125
local MENU_WIDTH			= 190
local MENU_ITEM_SPACING		= 1

-- цвета элементов меню
local MENU_COLOR 			= {r = 153, g = 238, b = 153, a = 255}
local TEXT_COLOR 			= {r = 255, g = 255, b = 255, a = 255}
local CURSOR_HOVER_COLOR 	= {r = 255, g = 170, b = 255, a = 255}
local DESCRIPTION_COLOR 	= {r = 255, g = 230, b = 100, a = 255}
local DESCRIPTION_FONT_SIZE	= 0.75

-- позиции меню
local MENU_X = 45
local MENU_Y = 240

-- аудио идентификаторы
local SOUND_CLICK          = 1083
local SOUND_BACK           = 1084
local SOUND_SCROLL         = 1058

-- текущее состояние
local currentMenu 		= "MainMenu"	-- текущее активное меню
local wasLMBDown		= false			-- состояние ЛКМ 				в предыдущем кадре
local wasRMBDown		= false			-- состояние ПКМ 				в предыдущем кадре
local wasUArrowDown 	= false			-- состояние стрелки вверх		в предыдущем кадре
local wasDArrowDown 	= false			-- состояние стрелки вниз		в предыдущем кадре
local wasLArrowDown 	= false			-- состояние стрелки влево		в предыдущем кадре
local wasRArrowDown 	= false			-- состояние стрелки вправо		в предыдущем кадре
local wasEnterDown 		= false			-- состояние клавиши Enter		в предыдущем кадре
local wasBackspaceDown 	= false			-- состояние клавиши Backspace	в предыдущем кадре
local focusIndex 		= 1				-- индекс текущего выбранного элемента меню
local holdStartTimeU 	= nil  			-- vремя начала удержания вверх стрелки
local holdStartTimeD 	= nil  			-- vремя начала удержания вниз стрелки
local holdStartTimeL 	= nil  			-- vремя начала удержания влево стрелки
local holdStartTimeR 	= nil  			-- vремя начала удержания вправо стрелки
local holdDelay 		= 0.500			-- zадержка перед первой активацией (мс)
local holdInterval		= 0.200			-- iнтервал повторения действия (мс)

-- переменные для текстовой анимации
local textAnimation = "..."
local lastUpdateTime = os.time()
local updateInterval = 0.5  

-- переменные для положения камеры
local camhack_status = true            -- Статус КамХака
local cam_slot = 1                     -- Текущая камера слот


-- ================== ОПРЕДЕЛЕНИЕ МЕНЮ ==================

local menus = function() 
    return {
        MainMenu = {
            title = "Main Menu",
            parent = nil,  
            items = {
                {
                    label = "Positions", 
                    action = function() 
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        currentMenu = 'PosMenu' 
                        --printStringNow("Positions action triggered!", 500) 
                    end, 
                    description = "Configure camera positions"
                },
                {
                    label = "Playback",
                    action = function() 
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        --printStringNow("Playback action triggered!", 500) 
                    end, 
                    description = "Play recorded audio"
                },
                {
                    label = "Pause", 
                    action = function()
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        --printStringNow("Pause action triggered!", 500) 
                    end, 
                    description = "Pause current action"
                },
                {
                    label = "Car slot", 
                    action = function() 
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        --printStringNow("Car slot action triggered!", 500) 
                    end, 
                    description = "Manage car storage"
                },
                {
                    label = "Save/Load", 
                    action = function() 
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        --printStringNow("Save/Load action triggered!", 500) 
                    end, 
                    description = "Save or load settings"
                },
                {
                    label = "Triggers", 
                    action = function() 
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        --printStringNow("Triggers action triggered!", 500) 
                    end, 
                    description = "Configure trigger actions"
                },
                {
                    label = "Options", 
                    action = function()
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        --printStringNow("Options action triggered!", 500) 
                    end, 
                    description = "Adjust settings"
                },
            }
        },

        PosMenu = {
            title = "Positions",
            parent = "MainMenu",
            items = {
                {
                    label = "Camhack: " .. (camhack_status and "~g~~h~~h~~h~ON" or "~r~~h~~h~~h~OFF"), 
                    action = function() 
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        --printStringNow("Toggle CamHack!", 500) 
                        camhack_status = not camhack_status 
                    end, 
                    description = "Toggle Camhack"
                },
                {
                    label = "Cam Slot: " .. cam_slot, 
                    action = function() 
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        --printStringNow("Display Current Camera Slot!", 500) 
                    end, 
                    scrollAction = adjustCamSlot, 
                    description = "Select slot to Load or Save"
                },
                {
                    label = "Preview", 
                    action = function() 
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        --printStringNow("Preview Camera Position!", 500) 
                    end, 
                    description = "Preview current camera"
                },
                {
                    label = "Save", 
                    action = function() 
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        --printStringNow("Save Camera Position!", 500) 
                    end, 
                    description = "Save current camera position to slot"
                },
                {
                    label = "Load", 
                    action = function() 
						addOneOffSound(0, 0, 0, SOUND_CLICK)
                        --printStringNow("Load Camera Position!", 500) 
                    end, 
                    description = "Load camera position from slot"
                },
                {
                    label = "", 
                    action = function() 
                    end, 
                    description = ""
                },
                {
                    label = "Back", 
                    action = function() 
                        currentMenu = 'MainMenu'
						focusIndex	= 1
						addOneOffSound(0, 0, 0, SOUND_BACK)
                        --printStringNow("Back to Main Menu!", 500) 
                    end, 
                    description = "Return to the main menu"
                },
            }
        }
    }
end

-- ================== ФУНКЦИИ ==================

function sign(x)
    return x > 0 and 1 or x < 0 and -1 or 0
end

function adjustCamSlot(mouseDelta, fastModifier, preciseModifier)
    local step = fastModifier and mouseDelta * 3 or preciseModifier and sign(mouseDelta) or mouseDelta
    addOneOffSound(0, 0, 0, SOUND_SCROLL)
    cam_slot = math.max(1, cam_slot + step)
end

-- Draw a text with given attributes
function drawText(text, x, y, size, color, isHovered)
    local style = mad.font_style.FONT_SUBTITLES
    local width = size * 0.58
    local height = size * 1.02
    local align = mad.font_align.LEFT
    local wrap = MENU_WIDTH
    local proportional = true
    local justify = false  

    local textColor = isHovered and CURSOR_HOVER_COLOR or color
    mad.draw_text(text, x, y, style, width, height, align, wrap, proportional, justify, 
                  textColor.r, textColor.g, textColor.b, textColor.a, 
                  1, 0, 0, 0, 0, 255, 0, 0, 0, 0)
end

-- Check if a point is within a rectangular area
function isPointInArea(pointX, pointY, rectX, rectY, rectWidth, rectHeight)
    return pointX >= rectX and pointX <= rectX + rectWidth and
           pointY >= rectY and pointY <= rectY + rectHeight
end

-- Obnovit? funkciu otobrazenia menu
function displayMenu(menu)
	if not isMenuActive then return end

    -- Inicializacia lokal?nyh peremennyh
    local cursorX, cursorY		= getCursorPos()		-- Tekusaa pozicia kursora
    local isLMBDown				= isKeyDown(1)  		-- ?LKM? nazata
    local isRMBDown				= isKeyDown(2)  		-- ?PKM? nazata
    local currentDescription	= ""  					-- Tekusee opisanie
    local isArrowKeyDown		= false  				-- Flag nazatia strelki
	local isAnyItemHovered		= false
    local mouseWheelDelta		= getMousewheelDelta()  -- Prokrutka kolesa mysi
    local isUArrowDown			= isKeyDown(38)  		-- Vverh strelka nazata
    local isDArrowDown			= isKeyDown(40)  		-- Vniz strelka nazata
    local isLArrowDown			= isKeyDown(37)  		-- Vlevo strelka nazata
    local isRArrowDown			= isKeyDown(39)  		-- Vpravo strelka nazata
    local isEnterDown			= isKeyDown(13)  		-- Enter nazat
	local isShiftDown			= isKeyDown(16)
	local isCtrlDown			= isKeyDown(18)			-- на самом деле альт
    local isBackspaceDown		= isKeyDown(8)			-- Backspace nazat

    -- Proverka na aktivnosti SF, Chata, Dialoga i Menu pauzy
    local isNotInRestrictedMode = not isSampfuncsConsoleActive() and not sampIsChatInputActive() and not sampIsDialogActive() and not isPauseMenuActive()

    -- ?Otrisovka? zagolovka menu
    drawText(menu.title, MENU_X, MENU_Y, MENU_TITLE_SIZE, MENU_COLOR, false)

    -- Perebor elementov menu
    for i, item in ipairs(menu.items) do
        local itemY = MENU_Y + 10 + (MENU_ITEM_HEIGHT + MENU_ITEM_SPACING) * i

        if isNotInRestrictedMode then
            local isHovered = isPointInArea(cursorX, cursorY, MENU_X, itemY, MENU_ITEM_WIDTH, MENU_ITEM_HEIGHT)
            if isHovered then isAnyItemHovered = true end
			
            -- Obnovlenie fokusa na osnove navedenia mysi, esli strelki ne byli nazaty
            if isHovered and not isArrowKeyDown then
                focusIndex = i
            end
        end

        -- Opredelenie, sfokusirovan li element ili na nego naveden kursor
        local isFocused = (i == focusIndex)
        drawText(item.label, MENU_X, itemY, 1, TEXT_COLOR, isFocused or (isNotInRestrictedMode and isPointInArea(cursorX, cursorY, MENU_X, itemY, MENU_ITEM_WIDTH, MENU_ITEM_HEIGHT)))

        -- Vypolnenie dejstvij v zavisimosti ot sostoania fokusa
        if isFocused then
            currentDescription = item.description

            -- Obrabotka prokrutki kolesom mysi ili strelkami с учетом удержания стрелок
            local currentTime = socket.gettime()

            -- Удержание левой стрелки (скролл влево)
            if isLArrowDown then
                if not wasLArrowDown then
                    holdStartTimeL = currentTime
                    if item.scrollAction then item.scrollAction(-1, isShiftDown, isCtrlDown) end
                elseif currentTime - holdStartTimeL >= holdDelay then
                    if item.scrollAction then item.scrollAction(-1, isShiftDown, isCtrlDown) end
                    holdStartTimeL = currentTime - (holdDelay - holdInterval)
                end
            else
                holdStartTimeL = nil
            end

            -- Удержание правой стрелки (скролл вправо)
            if isRArrowDown then
                if not wasRArrowDown then
                    holdStartTimeR = currentTime
					print('f', holdStartTimeR)
                    if item.scrollAction then item.scrollAction(1, isShiftDown, isCtrlDown) end
                elseif currentTime - holdStartTimeR >= holdDelay then
                    if item.scrollAction then item.scrollAction(1, isShiftDown, isCtrlDown) end
                    holdStartTimeR = currentTime - (holdDelay - holdInterval)
                end
				print(currentTime - holdStartTimeR)
            else
                holdStartTimeR = nil
            end

            -- Obrabotka nazatia ?LKM? ili Enter
			if isNotInRestrictedMode and (
				(isLMBDown and not wasLMBDown and isAnyItemHovered) or
				(isEnterDown and not wasEnterDown)
			) then
				item.action()
			end
        end
    end
	
	-- Прокрутка вверх с удержанием
	local currentTime = socket.gettime()
	if isUArrowDown then
		if not wasUArrowDown then
			holdStartTimeU = currentTime
			repeat
				if focusIndex == 1 then
					focusIndex = #menu.items  -- Go to the last element
				else
					focusIndex = focusIndex - 1
				end
			until menu.items[focusIndex].label ~= ""
		elseif currentTime - holdStartTimeU >= holdDelay then
			repeat
				if focusIndex == 1 then
					focusIndex = #menu.items  -- Go to the last element
				else
					focusIndex = focusIndex - 1
				end
			until menu.items[focusIndex].label ~= ""
			holdStartTimeU = currentTime - (holdDelay - holdInterval)
		end
	else
		holdStartTimeU = nil
	end

	-- Прокрутка вниз с удержанием
	if isDArrowDown then
		if not wasDArrowDown then
			holdStartTimeD = currentTime
			repeat
				if focusIndex == #menu.items then
					focusIndex = 1  -- Go to the first element
				else
					focusIndex = focusIndex + 1
				end
			until menu.items[focusIndex].label ~= ""
		elseif currentTime - holdStartTimeD >= holdDelay then
			repeat
				if focusIndex == #menu.items then
					focusIndex = 1  -- Go to the first element
				else
					focusIndex = focusIndex + 1
				end
			until menu.items[focusIndex].label ~= ""
			holdStartTimeD = currentTime - (holdDelay - holdInterval)
		end
	else
		holdStartTimeD = nil
	end
	
    -- Otobrazenie tekusego opisania pod poslednim elementom menu
    local descriptionY = MENU_Y + 15 + (MENU_ITEM_HEIGHT + MENU_ITEM_SPACING) * (#menu.items + 1)
    drawText(currentDescription, MENU_X, descriptionY, DESCRIPTION_FONT_SIZE, DESCRIPTION_COLOR, false)

    -- Pereklucenie na roditel?skoe menu, esli nazata ?PKM ili Backspace
    if isNotInRestrictedMode and ((isRMBDown and not wasRMBDown and isAnyItemHovered) or (isBackspaceDown and not wasBackspaceDown)) then
		if menu.parent then
			currentMenu = menu.parent
		else
			isMenuActive = false
			showCursor(false, false)
		end
		addOneOffSound(0, 0, 0, SOUND_BACK)
		if not isAnyItemHovered then	-- obnulenie fokusa na pervyj ?item
			focusIndex = 1				-- esli ni odin iz ?ajtemov? ne ?hovered?
		end
    end

    -- Obnovlenie peremennyh sostoania klavis
    wasUArrowDown 		= isUArrowDown
    wasDArrowDown 		= isDArrowDown
    wasLArrowDown	 	= isLArrowDown
    wasRArrowDown 		= isRArrowDown
    wasLMBDown			= isLMBDown
    wasRMBDown			= isRMBDown
    wasEnterDown		= isEnterDown
    wasBackspaceDown	= isBackspaceDown
end



function updateText()
    local currentTime = socket.gettime()

    if currentTime - lastUpdateTime >= updateInterval then
        if textAnimation == "." then
            textAnimation = ".."
        elseif textAnimation == ".." then
            textAnimation = "..."
        else
            textAnimation = "."
        end

        --print(textAnimation)

        lastUpdateTime = currentTime
    end
end


-- Main function
function main()
    while not isPlayerPlaying(PLAYER_HANDLE) do wait(100) end
	sampRegisterChatCommand('llc', function() isMenuActive = not isMenuActive showCursor(isMenuActive, isMenuActive) end)
    wait(100)

    while true do wait(0)
		--print(getMousewheelDelta())

		updateText()
        -- Display the current active menu
        local menu = menus()[currentMenu]
        if menu then displayMenu(menu) end
    end
end


--    if hoveredItem then
--        if mouseWheelDelta > 0 and hoveredItem.scrollUp then
--            hoveredItem.scrollUp()
--        elseif mouseWheelDelta < 0 and hoveredItem.scrollDown then
--            hoveredItem.scrollDown()
--        end
--    end

--[[
   ___ __  __  __  
	| /  \|  \/  \ .
	| \__/|__/\__/ .

  >> добавить включение курсора при открытии меню и выключение при закрытии   - ✓
  >> заменить айди звуков на константы в начале кода                          - ✓
  >> добавить проверку на ховер при нажатии кнопок мыши                       - ✓ 
  >> добавить проверку RestrictedMode всем клавишам, у которых ее ещё нет	  - ✓
  >> добавить циклическую обработки кнопок, при их долгом удержании           - ✓
  >> при отсутствии ховера и переключении с клавиатуры скипать пустые айтемы  - ✓
  

]]--