Tornado Geek GUI
Key RequiredUniversal

Tornado Geek GUI

Universal ScriptZX_the_randomZX_the_random 262 views 6.2K executesUpdated Jul 20, 2026
Unrated
00
Lua Script•3166 lines (119010 chars)
-- Carrega a WindUI (biblioteca de interface)
local WindUI = loadstring(game:HttpGet("https://github.com/Footagesus/WindUI/releases/latest/download/main.lua"))()

-- =================[ Serviços ]================--
local RunService = game:GetService("RunService")
local PathfindingService = game:GetService("PathfindingService")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local Player = Players.LocalPlayer
local Workspace = game:GetService("Workspace")
local Debris = game:GetService("Debris")
local Lighting = game:GetService("Lighting")

-- =================[ Sistema de Health para Parthes ]================--
local PartHealth = {}
local PartHealthMax = {}

local function InitializePartHealth(part)
    if not PartHealth[part] then
        local size = part.Size.Magnitude
        local health = 0
        if size < 5 then
            health = 1
        elseif size < 15 then
            health = 2
        elseif size < 30 then
            health = 3
        elseif size < 60 then
            health = 4
        else
            health = 5
        end
        PartHealth[part] = health
        PartHealthMax[part] = health
    end
end

local function DamagePart(part, damage)
    InitializePartHealth(part)
    PartHealth[part] = PartHealth[part] - damage
    if PartHealth[part] <= 0 then
        part:Destroy()
        PartHealth[part] = nil
        PartHealthMax[part] = nil
        return true
    end
    return false
end

local function CanLiftPartByEF(part, efLevel)
    InitializePartHealth(part)
    local currentHealth = PartHealth[part] or 0
    local maxHealth = PartHealthMax[part] or 0
    local healthPercent = maxHealth > 0 and (currentHealth / maxHealth) or 0
    
    local healthMultiplier = 1
    if healthPercent < 0.5 then
        healthMultiplier = 0.5
    elseif healthPercent < 0.3 then
        healthMultiplier = 0.3
    end
    
    local mass = part:GetMass()
    local size = part.Size.Magnitude
    
    -- Capacidades por EF (EF0 a EF6)
    local liftCapabilities = {
        [0] = {mass = 30, size = 5},
        [1] = {mass = 50, size = 8},
        [2] = {mass = 80, size = 12},
        [3] = {mass = 120, size = 18},
        [4] = {mass = 200, size = 25},
        [5] = {mass = 350, size = 35},
        [6] = {mass = 500, size = 45},
    }
    
    local cap = liftCapabilities[efLevel] or liftCapabilities[6]
    return mass <= cap.mass * healthMultiplier and size <= cap.size * healthMultiplier * 1.5
end

local function CleanupPartData()
    for part, _ in pairs(PartHealth) do
        if not part or not part.Parent then
            PartHealth[part] = nil
            PartHealthMax[part] = nil
        end
    end
end

-- =================[ Sistema de Puxar Jogador ]================--
local PlayerPullData = {}

-- =================[ Variáveis globais ]================--
local ActiveTornados = {}
local TornadoCounter = 0
local ShowTrail = false
local TrailParts = {}
local WindfieldParts = {}
local ShowWindfield = true
local AutoSpawnCoroutine = nil
local AutoSpawnEnabled = false
local SupercellPart = nil
local SupercellBlocks = {}
local SupercellGridEnabled = false
local SupercellFogActive = false
local shrinkCoroutine = nil
local shrinkTarget = nil

-- Variáveis para Preview
local PreviewModel = nil
local PreviewSegments = {}
local PreviewBillboard = nil
local PreviewInfoGUI = nil

local originalFogStart = Lighting.FogStart
local originalFogEnd = Lighting.FogEnd
local originalFogColor = Lighting.FogColor
local isNearTornado = false

-- Tabela de configurações
local Settings = {
    EF_Level = 0,
    EF_Scale = 10.0,
    TornadoHeight = 710,
    MaxWindSpeed = 34,
    BaseWidth = 90,
    WidthIncrement = 0.05,
    TornadoType = "Cone",          -- sem Multi-Vortex
    Mutation = "Normal",           -- Normal, Mutant, Corrupted, Cursed, Radioactive, Violent, Firenado
    PhysicsMode = "Realistic",
    PullStrengthMult = 1.0,
    RadiusMult = 1.0,
    LiftSpeed = 1.8,
    RotationSpeed = 1.0,
    DebrisDensity = 210,
    FunnelTrans = 0.6,
    Color_R = 100,
    Color_G = 100,
    Color_B = 105,
    WanderSpeed = 25,
    WidthMultiplier = 1.0,
    ShowTrail = false,
    TrailColor = Color3.fromRGB(255, 190, 0),
    ShowMesonet = false,
    ShowWindfield = false,
    ShowHUD = true,
    GrowDuration = 190,
    TornadoLifetime = 1065,
    DirectionChangeSpeed = 0.2,
    OcclusionAngle = 180,
    SpawnPosX = 1,
    SpawnPosZ = 1,
    SatelliteCount = 2,              -- NOVO: quantidade de satélites
    SatelliteMaxWidth = 12,
    SatelliteOrbitMult = 2.0,
    GrowthBottomLagExponent = 1.2,
    IntroDuration = 3,
    OutroDuration = 5,
    AutoSpawnMinInterval = 7,
    AutoSpawnMaxInterval = 90,
    ShowPreview = false,             -- NOVO: toggle de pré-visualização
    Special = {
        MultiVortex = false,
        Satellites = false,
    },
}

-- Escalas EF (EF0 a EF6)
local EF_SCALES = {
    [0] = 10.0,
    [1] = 15.1,
    [2] = 20.2,
    [3] = 25.3,
    [4] = 30.5,
    [5] = 35.8,
    [6] = 40.0,
}

-- 5 TIPOS DE FORMATO (sem Multi-Vortex)
local SHAPE_FUNCTIONS = {
    ["Cone"] = function(ratio) return 0.8 + 0.7 * ratio end,
    ["Wedge"] = function(ratio) return 1.3 + 1.4 * ratio end,
    ["Rope"] = function(ratio) return 0.05 + 0.1 * ratio end,
    ["Bowl"] = function(ratio) return 1.0 - math.abs(ratio - 0.5) * 1.5 end,
    ["Dust Devil"] = function(ratio) return 0.7 + 0.5 * math.sin(ratio * math.pi) end,
}
-- Multi-Vortex será tratado separadamente via Special

setmetatable(SHAPE_FUNCTIONS, {__index = function() return SHAPE_FUNCTIONS["Cone"] end})

-- =================[ Função de tamanho por EF (EF0-EF6) ]================
local function IsSizeInEFRange(size, efLevel)
    local sizeRanges = {
        [0] = {2, 7},
        [1] = {10, 17},
        [2] = {20, 35},
        [3] = {40, 51},
        [4] = {60, 90},
        [5] = {100, 110},
        [6] = {134, 189},
    }
    local range = sizeRanges[efLevel]
    return range and size >= range[1] and size <= range[2]
end

-- =================[ WindUI ]================--
WindUI:AddTheme({
    Name = "Default",
    Accent = Color3.fromHex("#18181b"),
    Background = Color3.fromHex("#878787"),
    Outline = Color3.fromHex("#1f1f1f"),
    Text = Color3.fromHex("#FFFFFF"),
    Placeholder = Color3.fromHex("#7a7a7a"),
    Button = Color3.fromHex("#5c584e"),
    Icon = Color3.fromHex("#FFFFFF"),
})

local Window = WindUI:CreateWindow({
    Title = "Tornado-Geek GUI | V1.0",
    Icon = "tornado",
    Author = "Por: Christopher ZX | universal",
    Folder = "tornado-gui.FILECONFG",
    Size = UDim2.fromOffset(712, 465),
    MinSize = Vector2.new(340, 170),
    MaxSize = Vector2.new(699, 465),
    Transparent = false,
    Theme = "Dark",
    Resizable = true,
    SideBarWidth = 210,
    BackgroundImageTransparency = 0.12,
    HideSearchBar = true,
    ScrollBarEnabled = true,
    User = {
        Enabled = true,
        Anonymous = false,
        Callback = function() print("User clicked") end,
    },
})

Window:EditOpenButton({
    Title = "Tornado Geek | V1.0",
    Icon = "tornado",
    CornerRadius = UDim.new(0, 16),
    StrokeThickness = 1.4,
    Color = ColorSequence.new(Color3.fromHex("#ffffff"), Color3.fromHex("#f0f0f0")),
    OnlyMobile = false,
    Enabled = true,
    Draggable = true
})

-- =================[ Abas ]================--
local TabInfo     = Window:Tab({ Title = "info",   Icon = "info" })
Window:Divider()
local TabSpawn    = Window:Tab({ Title = "spawn storm", Icon = "tornado" })
local TabPhysics  = Window:Tab({ Title = "Physics", Icon = "settings" })
local TabESP      = Window:Tab({ Title = "Tornado ESP", Icon = "eye" })
local TabWarnings = Window:Tab({ Title = "NWS Warnings", Icon = "tornado" })
local TabRadar = Window:Tab({ Title = "radar", Icon = "radar" })
Window:Divider()
local TabGive     = Window:Tab({ Title = "give items", Icon = "ax" })
local TabPlayer   = Window:Tab({ Title = "local player", Icon = "user" })

-- =================[ Conteúdo Info ]================--
TabInfo:Section({ Title = "Informações do Hub" })
TabInfo:Paragraph({
    Title = "Tornado-Geek • Private Access",
    Desc = " •Tornado Spawner \n •Client Side \n •For videos",
    Color = nil
})
TabInfo:Section({ Title = "Status Atual" })
TabInfo:Paragraph({Title = "Servidor", Desc = "universal", Color = nil})
TabInfo:Paragraph({Title = "Versão", Desc = "V1.0 • EF0-EF6 • Novo Sistema de Dano e Física", Color = nil})
TabInfo:Button({
    Title = "Anti-Lag (Baixo Gráfico)",
    Desc = "Reduz lag drasticamente",
    Callback = function()
        settings().Rendering.QualityLevel = Enum.QualityLevel.Level01
        game.Lighting.GlobalShadows = false
        game.Lighting.FogEnd = 999999
        game.Lighting.Brightness = 2
        WindUI:Notify({Title = "Anti-Lag", Content = "Gráficos otimizados!", Duration = 5})
    end
})
TabInfo:Button({
    Title = "Carregar Infinite Yield",
    Desc = "Admin commands",
    Callback = function()
        loadstring(game:HttpGet('https://raw.githubusercontent.com/EdgeIY/infiniteyield/master/source'))()
    end
})

-- =================[ Spawn Storm - Controles principais ]================--
TabSpawn:Section({ Title = "Spawnar Tornados" })

local SpawnButton = TabSpawn:Button({
    Title = "SPAWN TORNADO",
    Desc = "Gera um novo tornado com as configs atuais",
    Callback = function()
        SpawnTornado()
    end
})

local DissipateButton = TabSpawn:Button({
    Title = "DISSIPATE TORNADO",
    Desc = "remove o tornado mais proximo",
    Callback = function()
        if state then
            local char = Player.Character
            local root = char and char:FindFirstChild("HumanoidRootPart")
            if not root then
                WindUI:Notify({Title = "Erro", Content = "Personagem não encontrado!", Duration = 3})
                return
            end
            local nearest = nil
            local minDist = math.huge
            for _, t in ipairs(ActiveTornados) do
                if t.Active and t.state ~= "dead" and t.state ~= "outro" then
                    local dist = (t.Core.Position - root.Position).Magnitude
                    if dist < minDist then
                        minDist = dist
                        nearest = t
                    end
                end
            end

            if not nearest then
                WindUI:Notify({Title = "Aviso", Content = "Nenhum tornado ativo encontrado para dissipar.", Duration = 3})
                return
            end

            shrinkTarget = nearest
            shrinkCoroutine = task.spawn(function()
                while shrinkTarget and shrinkTarget.Active and shrinkTarget.state ~= "dead" do
                    shrinkTarget.currentScale = math.max(0.01, shrinkTarget.currentScale - 0.05)
                    UpdateFunnelSizes(shrinkTarget)
                    if shrinkTarget.currentScale <= 0.02 then
                        shrinkTarget.state = "outro"
                        shrinkTarget.outroStartTime = tick()
                        shrinkTarget.outroDuration = 1
                        shrinkTarget = nil
                        break
                    end
                    task.wait()
                end
                shrinkTarget = nil
                shrinkCoroutine = nil
            end)

            WindUI:Notify({Title = "Dissipação", Content = "Encolhendo tornado mais próximo...", Duration = 3})
        else
            if shrinkTarget then
                shrinkTarget = nil
            end
            if shrinkCoroutine then
                task.cancel(shrinkCoroutine)
                shrinkCoroutine = nil
            end
            WindUI:Notify({Title = "Dissipação", Content = "Processo interrompido.", Duration = 3})
        end
    end
})

local TwinSpawnButton = TabSpawn:Button({
    Title = "SPAWN TWIN TORNADO",
    Desc = "Gera dois tornados gêmeos orbitando entre si",
    Callback = function()
        SpawnTwinTornados()
    end
})

TabSpawn:Button({
    Title = "DESPAWN ALL",
    Desc = "Remove todos os tornados",
    Callback = function()
        DestroyAllTornados()
    end
})

TabSpawn:Divider()

TabSpawn:Button({
    Title = "REDIRECIONAR PARA SPAWN",
    Desc = "Faz todos os tornados ativos se moverem em direção ao ponto de spawn definido",
    Callback = function()
        RedirectTornadosToSpawn()
    end
})

TabSpawn:Button({
    Title = "REDIRECIONAR PARA PLAYER",
    Desc = "Faz todos os tornados ativos perseguirem o jogador",
    Callback = function()
        RedirectTornadosToPlayer()
    end
})

TabSpawn:Button({
    Title = "Clear Fog",
    Desc = "reduz o fog",
    Callback = function()
        game.Lighting.GlobalShadows = false
        game.Lighting.FogEnd = 999999
        game.Lighting.Brightness = 2
    end
})

-- ===== NOVO AUTO SPAWN (único, 160-230s) =====
TabSpawn:Toggle({
    Title = "Auto Spawn (7-90s)",
    Default = false,
    Callback = function(state)
        AutoSpawnEnabled = state
        if state then
            if not AutoSpawnCoroutine then
                AutoSpawnCoroutine = task.spawn(function()
                    while AutoSpawnEnabled do
                        local delayTime = math.random(Settings.AutoSpawnMinInterval, Settings.AutoSpawnMaxInterval)
                        wait(delayTime)
                        if not AutoSpawnEnabled then break end
                        SpawnRandomTornado()
                    end
                    AutoSpawnCoroutine = nil
                end)
            end
        else
            if AutoSpawnCoroutine then
                task.cancel(AutoSpawnCoroutine)
                AutoSpawnCoroutine = nil
            end
        end
    end
})

TabSpawn:Divider()

-- =================[ Supercélula ]================--
TabSpawn:Section({ Title = "Supercélula" })

TabSpawn:Toggle({
    Title = "Supercélula (pequeno)",
    Default = false,
    Callback = function(state)
        if state then
            if SupercellGridEnabled then
                RemoveSupercellGrid()
                SupercellGridEnabled = false
            end
            if SupercellPart then SupercellPart:Destroy() end
            SupercellPart = Instance.new("Part")
            SupercellPart.Name = "SupercellCloud"
            SupercellPart.Anchored = true
            SupercellPart.CanCollide = false
            SupercellPart.Size = Vector3.new(650000, 65, 650000)
            SupercellPart.Position = Vector3.new(0, 600, 0)
            SupercellPart.Color = Color3.fromRGB(128, 128, 128)
            SupercellPart.Material = Enum.Material.SmoothPlastic
            SupercellPart.Transparency = 0.0
            SupercellPart.Parent = Workspace
            WindUI:Notify({Title = "Supercélula", Content = "Supercélula cinza adicionada a 600 studs.", Duration = 3})
        else
            if SupercellPart then
                SupercellPart:Destroy()
                SupercellPart = nil
            end
            WindUI:Notify({Title = "Supercélula", Content = "Supercélula removida.", Duration = 3})
        end
    end
})

TabSpawn:Toggle({
    Title = "Supercélula (grande)",
    Default = false,
    Callback = function(state)
        SupercellGridEnabled = state
        if state then
            if SupercellPart then
                SupercellPart:Destroy()
                SupercellPart = nil
            end
            CreateSupercellGrid()
            SupercellFogActive = true
            if not isNearTornado then
                Lighting.FogStart = 80
                Lighting.FogEnd = 250
                Lighting.FogColor = Color3.fromRGB(180, 190, 200)
            end
            WindUI:Notify({Title = "Supercélula", Content = "Grade de blocos criada. Fog ativado.", Duration = 3})
        else
            RemoveSupercellGrid()
            SupercellFogActive = false
            if not isNearTornado then
                Lighting.FogStart = originalFogStart
                Lighting.FogEnd = originalFogEnd
                Lighting.FogColor = originalFogColor
            end
            WindUI:Notify({Title = "Supercélula", Content = "Supercélula removida. Fog desativado.", Duration = 3})
        end
    end
})

TabSpawn:Divider()

TabSpawn:Section({ Title = "Configurações do Tornado" })

TabSpawn:Dropdown({
    Title = "Escala EF",
    Values = {
        {Title="EF0"}, {Title="EF1"}, {Title="EF2"}, {Title="EF3"},
        {Title="EF4"}, {Title="EF5"}, {Title="EF6"}
    },
    Default = "EF0",
    Callback = function(opt)
        local efNumber = tonumber(string.sub(opt.Title, 3)) or 0
        Settings.EF_Level = efNumber
        Settings.EF_Scale = EF_SCALES[efNumber]
        if Settings.ShowPreview then
            DestroyPreview()
            CreatePreview()
        end
    end
})

TabSpawn:Slider({
    Title = "Altura do Tornado",
    Value = {Min = 670, Max = 710, Default = 670},
    Callback = function(v)
        Settings.TornadoHeight = v
        if Settings.ShowPreview then
            DestroyPreview()
            CreatePreview()
        end
    end
})

-- Slider com máximo 525
TabSpawn:Slider({
    Title = "Max Windspeed",
    Value = {Min = 10, Max = 525, Default = 10},
    Callback = function(v)
        Settings.MaxWindSpeed = v
        if Settings.ShowPreview then
            DestroyPreview()
            CreatePreview()
        end
    end
})

TabSpawn:Slider({
    Title = "Max Tornado Width (escala extra)",
    Value = {Min = 0.2, Max = 17.0, Default = 0.2},
    Callback = function(v)
        Settings.WidthMultiplier = v
        if Settings.ShowPreview then
            DestroyPreview()
            CreatePreview()
        end
    end
})

TabSpawn:Slider({
    Title = "Grow Duration (s)",
    Value = {Min = 5, Max = 1450, Default = 190},
    Callback = function(v)
        Settings.GrowDuration = v
    end
})

TabSpawn:Slider({
    Title = "Intro Duration (s)",
    Value = {Min = 0, Max = 150, Default = 3},
    Callback = function(v)
        Settings.IntroDuration = v
    end
})

TabSpawn:Slider({
    Title = "Outro Duration (s)",
    Value = {Min = 0, Max = 150, Default = 5},
    Callback = function(v)
        Settings.OutroDuration = v
        for _, t in ipairs(ActiveTornados) do
            if t.Active and t.state == "outro" then
                t.outroDuration = v
            end
        end
    end
})

TabSpawn:Slider({
    Title = "Tornado Lifetime (s)",
    Value = {Min = 5, Max = 9500, Default = 5},
    Callback = function(v)
        Settings.TornadoLifetime = v
        for _, t in ipairs(ActiveTornados) do
            if t.Active and t.state == "stable" then
                t.lifetime = v
                t.outroTriggerTime = t.stableStartTime + v
            end
        end
    end
})

TabSpawn:Slider({
    Title = "Posição X (spawn)",
    Value = {Min = -8900, Max = 8900, Default = 1},
    Callback = function(v)
        Settings.SpawnPosX = v
        if Settings.ShowPreview then
            DestroyPreview()
            CreatePreview()
        end
    end
})

TabSpawn:Slider({
    Title = "Posição Z (spawn)",
    Value = {Min = -8900, Max = 8900, Default = 1},
    Callback = function(v)
        Settings.SpawnPosZ = v
        if Settings.ShowPreview then
            DestroyPreview()
            CreatePreview()
        end
    end
})

-- ===== NOVO DROPDOWN DE MUTAÇÕES (renomeadas) =====
TabSpawn:Dropdown({
    Title = "Mutação",
    Values = {
        {Title="Normal"}, {Title="Mutant"}, {Title="Corrupted"},  -- era Cursed
        {Title="Cursed"},  -- era Monster
        {Title="Radioactive"}, {Title="Violent"}, {Title="Firenado"}
    },
    Default = "Normal",
    Callback = function(opt)
        Settings.Mutation = opt.Title
        for _, t in ipairs(ActiveTornados) do
            UpdateMutationEffects(t)
        end
        if Settings.ShowPreview then
            DestroyPreview()
            CreatePreview()
        end
    end
})

-- ===== TIPO DE TORNADO (sem Multi-Vortex) =====
TabSpawn:Dropdown({
    Title = "Tipo de Tornado",
    Values = {
        {Title="Cone"},
        {Title="Wedge"},
        {Title="Rope"},
        {Title="Bowl"},
        {Title="Dust Devil"}
    },
    Default = "Cone",
    Callback = function(opt)
        Settings.TornadoType = opt.Title
        if Settings.ShowPreview then
            DestroyPreview()
            CreatePreview()
        end
    end
})

-- ===== DROPDOWN SPECIAL (multi-seleção) com "None" =====
local SpecialDropdown = TabSpawn:Dropdown({
    Title = "Special (multi)",
    Values = {
        {Title="None"},
        {Title="Multi-Vortex"},
        {Title="Satellites"}
    },
    Default = "Nenhum",
    Multi = true,
    Callback = function(selected)
        Settings.Special.MultiVortex = false
        Settings.Special.Satellites = false
        local hasNone = false
        for _, opt in ipairs(selected) do
            if opt.Title == "None" then
                hasNone = true
            elseif opt.Title == "Multi-Vortex" then
                Settings.Special.MultiVortex = true
            elseif opt.Title == "Satellites" then
                Settings.Special.Satellites = true
            end
        end
        if hasNone then
            Settings.Special.MultiVortex = false
            Settings.Special.Satellites = false
        end

        -- Atualiza tornados existentes
        for _, t in ipairs(ActiveTornados) do
            if t.Active and t.state == "stable" then
                -- Remove inner vortices se Multi-Vortex desativado
                if not Settings.Special.MultiVortex and t.InnerVortices then
                    for _, v in ipairs(t.InnerVortices) do
                        for _, seg in ipairs(v.Segments) do
                            if seg.Part then seg.Part:Destroy() end
                        end
                    end
                    t.InnerVortices = nil
                end
                -- Remove satélites se desativado
                if not Settings.Special.Satellites and t.Satellites then
                    for _, sat in ipairs(t.Satellites) do
                        if sat and sat.Active then DestroyTornado(sat) end
                    end
                    t.Satellites = {}
                end
                GenerateFunnel(t)
                UpdateFunnelSizes(t)
                UpdateMutationEffects(t)
            end
        end
        if Settings.ShowPreview then
            DestroyPreview()
            CreatePreview()
        end
    end
})

-- ===== NOVO TOGGLE DE PRÉ-VISUALIZAÇÃO =====
TabSpawn:Toggle({
    Title = "Show Pre-Spawn Preview",
    Default = false,
    Callback = function(state)
        Settings.ShowPreview = state
        if state then
            CreatePreview()
        else
            DestroyPreview()
        end
    end
})

-- ===== NOVO SLIDER DE QUANTIDADE DE SATÉLITES =====
TabSpawn:Slider({
    Title = "Satellite Count",
    Value = {Min = 0, Max = 10, Default = 2},
    Callback = function(v)
        Settings.SatelliteCount = v
        if Settings.ShowPreview then
            DestroyPreview()
            CreatePreview()
        end
    end
})

-- ===== TOGGLES DE VISUAL =====
TabSpawn:Toggle({
    Title = "Show Trail",
    Default = false,
    Callback = function(state)
        Settings.ShowTrail = state
        ShowTrail = state
        if not state then
            for _, part in ipairs(TrailParts) do
                if part then part:Destroy() end
            end
            table.clear(TrailParts)
        end
    end
})

TabSpawn:Toggle({
    Title = "Show Windfield",
    Default = true,
    Callback = function(state)
        Settings.ShowWindfield = state
        ShowWindfield = state
        if not state then
            for _, part in ipairs(WindfieldParts) do
                if part then part:Destroy() end
            end
            table.clear(WindfieldParts)
        end
    end
})

TabSpawn:Toggle({
    Title = "Show Mesonet",
    Default = false,
    Callback = function(state)
        Settings.ShowMesonet = state
        if not state then
            for _, t in ipairs(ActiveTornados) do
                if t.MesonetBeams then
                    for _, beam in ipairs(t.MesonetBeams) do beam:Destroy() end
                    t.MesonetBeams = {}
                end
            end
        end
    end
})

TabSpawn:Toggle({
    Title = "Show HUD",
    Default = true,
    Callback = function(state)
        Settings.ShowHUD = state
        if HudFrame then
            HudFrame.Visible = state
        end
    end
})

TabSpawn:Divider()

-- =================[ Warnings ]================--
TabWarnings:Section({ Title = "🌪️ Alerta Global" })
local GlobalWarningParagraph = TabWarnings:Paragraph({
    Title = "✅ Nenhum tornado avistado",
    Desc = "O céu está limpo.",
    Color = nil
})

local function UpdateWarnings()
    local char = Player.Character
    local root = char and char:FindFirstChild("HumanoidRootPart")
    if not root then
        GlobalWarningParagraph:SetTitle("Sem jogador")
        GlobalWarningParagraph:SetDesc("Nenhum personagem encontrado.")
        return
    end

    if #ActiveTornados == 0 then
        GlobalWarningParagraph:SetTitle("✅ Nenhum tornado avistado")
        GlobalWarningParagraph:SetDesc("O céu está limpo por enquanto.")
        return
    end

    local highestEF = 0
    local nearestDist = math.huge
    local inDanger = false

    for _, tornado in ipairs(ActiveTornados) do
        if tornado.Active and tornado.state ~= "dead" then
            local effectiveTopWidth = tornado.targetFullTopWidth * tornado.currentScale
            local dangerRadius = effectiveTopWidth * 6
            local dist = (tornado.Core.Position - root.Position).Magnitude
            if dist <= dangerRadius then
                inDanger = true
                if tornado.EF_Level > highestEF then
                    highestEF = tornado.EF_Level
                end
                if dist < nearestDist then
                    nearestDist = dist
                end
            end
        end
    end

    if not inDanger then
        GlobalWarningParagraph:SetTitle("⚠️ Tornados presentes")
        GlobalWarningParagraph:SetDesc("Você está fora da zona de perigo imediato.")
        return
    end

    local title, desc = "", ""
    if highestEF <= 0 then
        title = "🌀 Tiny Tornado Warning"
        desc = "Um tornado bem fraco foi detectado."
    elseif highestEF == 1 then
        title = "⚠️ Tornado Warning"
        desc = "Um tornado foi detectado. Fique atento."
    elseif highestEF <= 3 then
        title = "🔴 Tornado Alert PDS 🔴"
        desc = "Tornado particularmente perigoso. Abrigue-se imediatamente."
    elseif highestEF <= 5 then
        title = "⚠️🟣 Tornado Emergency 🟣⚠️"
        desc = "Tornado extremamente violento! Evacuação urgente."
    else
        title = "🌐⚫ Evacuation Needed ⚫🌐"
        desc = "Tornado catastrófico. Evacuação obrigatória."
    end

    GlobalWarningParagraph:SetTitle(title)
    GlobalWarningParagraph:SetDesc(string.format("%s\nDistância: ~%d studs (EF%d)", desc, math.floor(nearestDist), highestEF))
end

-- =================[ Radar ]================--
TabRadar:Section({ Title = "Radar System" })

local RadarGridParagraph = TabRadar:Paragraph({
    Title = "🟢Working | Radar Live",
    Desc = "⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛\n⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛\n⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛",
    Color = nil
})

local RadarInfoParagraph = TabRadar:Paragraph({
    Title = "Current Tornado info:",
    Desc = "EF:X | Total Windspeeds: 000 Mph/h | Actual Windspeeds: 000 Mph/h \nTotal Width: 16(Example) | Distance: 9000 Studs | Total Parts destroyed: 100(Example)",
    Color = nil
})

-- =================[ Atualização Radar ]================--
local SimulatedPartsDestroyed = 0

local function UpdateRadarInfo()
    local char = Player.Character
    local root = char and char:FindFirstChild("HumanoidRootPart")
    
    if #ActiveTornados == 0 then
        RadarInfoParagraph:SetDesc("EF:X | Total Windspeeds: 000 Mph/h | Actual Windspeeds: 000 Mph/h \nTotal Width: 16(Example) | Distance: 9000 Studs | Total Parts destroyed: 100(Example)")
        return
    end
    
    local nearestTornado = nil
    local minDist = math.huge
    
    for _, tornado in ipairs(ActiveTornados) do
        if tornado.Active and tornado.state ~= "dead" then
            local dist = root and (tornado.Core.Position - root.Position).Magnitude or math.huge
            if dist < minDist then
                minDist = dist
                nearestTornado = tornado
            end
        end
    end
    
    if not nearestTornado then
        RadarInfoParagraph:SetDesc("EF:X | Total Windspeeds: 000 Mph/h | Actual Windspeeds: 000 Mph/h \nTotal Width: 16(Example) | Distance: 9000 Studs | Total Parts destroyed: 100(Example)")
        return
    end
    
    local efLevel = nearestTornado.EF_Level
    local maxWind = math.floor(nearestTornado.MaxWindSpeed * 10) / 10
    local actualWind = math.floor((nearestTornado.MaxWindSpeed * nearestTornado.currentScale) * 10) / 10
    local width = math.floor(nearestTornado.targetFullTopWidth * nearestTornado.currentScale)
    local distance = root and math.floor(minDist) or 0
    
    SimulatedPartsDestroyed = SimulatedPartsDestroyed + math.random(0, 1)
    
    RadarInfoParagraph:SetDesc(string.format(
        "EF:%d | Total Windspeeds: %.1f Mph/h | Actual Windspeeds: %.1f Mph/h \nTotal Width: %d Studs | Distance: %d Studs | Total Parts destroyed: %d",
        efLevel, maxWind, actualWind, width, distance, SimulatedPartsDestroyed
    ))
end

local function UpdateRadarGrid()
    local char = Player.Character
    local root = char and char:FindFirstChild("HumanoidRootPart")
    if not root then
        RadarGridParagraph:SetDesc("⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛\n⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛\n⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛")
        return
    end

    local gridSize = 11
    local gridChars = {}
    local radarRadius = 4000

    for i = 1, gridSize * gridSize do gridChars[i] = "⬛" end

    for _, tornado in ipairs(ActiveTornados) do
        if tornado.Active and tornado.state ~= "dead" and tornado.state ~= "outro" then
            local diff = tornado.Core.Position - root.Position
            local dist = diff.Magnitude
            if dist <= radarRadius then
                local gridX = math.floor(((diff.X / radarRadius) * 0.5 + 0.5) * (gridSize - 1)) + 1
                local gridZ = math.floor(((diff.Z / radarRadius) * 0.5 + 0.5) * (gridSize - 1)) + 1
                if gridX >= 1 and gridX <= gridSize and gridZ >= 1 and gridZ <= gridSize then
                    local idx = gridX + (gridZ - 1) * gridSize
                    local tornadoColorCode = "⬜"
                    if tornado.EF_Level <= 1 then tornadoColorCode = "🟩"
                    elseif tornado.EF_Level <= 2 then tornadoColorCode = "🟨"
                    elseif tornado.EF_Level <= 3 then tornadoColorCode = "🟧"
                    elseif tornado.EF_Level <= 4 then tornadoColorCode = "🟥"
                    elseif tornado.EF_Level <= 5 then tornadoColorCode = "🟪"
                    else tornadoColorCode = "🟦" end
                    gridChars[idx] = tornadoColorCode
                end
            end
        end
    end

    local displayString = ""
    for z = 0, gridSize - 1 do
        for x = 0, gridSize - 1 do
            displayString = displayString .. gridChars[(x + 1) + z * gridSize]
        end
        if z < gridSize - 1 then displayString = displayString .. "\n" end
    end
    RadarGridParagraph:SetDesc(displayString)
end

task.spawn(function()
    while true do
        UpdateRadarInfo()
        UpdateRadarGrid()
        task.wait(1.5)
    end
end)

TabRadar:Section({ Title = "tornado history | Saved in: “tornado-gui.FILECONFG”." })

-- =================[ Give items - Mesonets ]================--
TabGive:Section({ Title = "Equipamento Científico" })
TabGive:Button({
    Title = "Receber Sonda Portátil (Mesonet)",
    Desc = "Adiciona uma sonda ao seu inventário",
    Callback = function()
        GiveProbeTool()
    end
})
TabGive:Button({
    Title = "Receber 5 Sondas",
    Desc = "Kit de campo",
    Callback = function()
        for i = 1, 5 do
            GiveProbeTool()
        end
    end
})

-- =================[ Tornado ESP ]================--
TabESP:Paragraph({ Title = "ESP do Tornado", Desc = "Visualização do tornado no mapa.", Color = nil })

-- =================[ Local Player ]================--
TabPlayer:Section({ Title = "Local Player" })
TabPlayer:Slider({
    Title = "WalkSpeed",
    Value = {Min = 0, Max = 100, Default = 16},
    Callback = function(v)
        local char = Player.Character
        if char and char:FindFirstChild("Humanoid") then
            char.Humanoid.WalkSpeed = v
        end
    end
})
TabPlayer:Slider({
    Title = "JumpPower",
    Value = {Min = 0, Max = 250, Default = 50},
    Callback = function(v)
        local char = Player.Character
        if char and char:FindFirstChild("Humanoid") then
            char.Humanoid.JumpPower = v
        end
    end
})
TabPlayer:Button({
    Title = "Resetar WalkSpeed & JumpPower",
    Desc = "Volta aos valores padrão",
    Callback = function()
        local char = Player.Character
        if char and char:FindFirstChild("Humanoid") then
            char.Humanoid.WalkSpeed = 16
            char.Humanoid.JumpPower = 50
            WindUI:Notify({Title = "Resetado!", Content = "WalkSpeed: 16 | JumpPower: 50", Duration = 3})
        end
    end
})

-- =================[ Physics ]================--
TabPhysics:Section({ Title = "Motor de Física" })

TabPhysics:Dropdown({
    Title = "Vortex Mode",
    Values = {{Title="Original"}, {Title="Better"}, {Title="Realistic"}},
    Default = "Realistic",
    Callback = function(opt)
        Settings.PhysicsMode = opt.Title
    end
})

TabPhysics:Slider({
    Title = "Pull Radius Mult",
    Value = {Min = 0.5, Max = 3.0, Default = 1.0},
    Callback = function(v) Settings.RadiusMult = v end
})

TabPhysics:Slider({
    Title = "Suck Strength Mult",
    Value = {Min = 0.5, Max = 5.0, Default = 1.0},
    Callback = function(v) Settings.PullStrengthMult = v end
})

TabPhysics:Slider({
    Title = "Updraft Lift Speed",
    Value = {Min = 1.5, Max = 3.0, Default = 1.5},
    Callback = function(v) Settings.LiftSpeed = v end
})

TabPhysics:Slider({
    Title = "Rotation Speed Mult",
    Value = {Min = 0.1, Max = 5.0, Default = 1.0},
    Callback = function(v) Settings.RotationSpeed = v end
})

TabPhysics:Slider({
    Title = "Direction Change Speed",
    Value = {Min = 0.01, Max = 2.0, Default = 0.2},
    Callback = function(v)
        Settings.DirectionChangeSpeed = v
        for _, t in ipairs(ActiveTornados) do
            t.directionChangeSpeed = v
        end
    end
})

TabPhysics:Slider({
    Title = "Occlusion Angle (°)",
    Value = {Min = 12, Max = 180, Default = 12},
    Callback = function(v)
        Settings.OcclusionAngle = v
    end
})

TabPhysics:Divider()

TabPhysics:Section({ Title = "Visual & Partículas" })

TabPhysics:Slider({
    Title = "Transparency",
    Value = {Min = 0.1, Max = 1.0, Default = 0.6},
    Callback = function(v)
        Settings.FunnelTrans = v
        for _, t in ipairs(ActiveTornados) do
            UpdateVisuals(t)
        end
    end
})

TabPhysics:Slider({
    Title = "Debris Amount",
    Value = {Min = 0, Max = 500, Default = 100},
    Callback = function(v)
        Settings.DebrisDensity = v
        for _, t in ipairs(ActiveTornados) do
            if t.DebrisEmitter then
                t.DebrisEmitter.Rate = v
            end
        end
    end
})

TabPhysics:Slider({
    Title = "Color: Red",
    Value = {Min = 0, Max = 255, Default = 100},
    Callback = function(v) Settings.Color_R = v; for _, t in ipairs(ActiveTornados) do UpdateVisuals(t) end end
})

TabPhysics:Slider({
    Title = "Color: Green",
    Value = {Min = 0, Max = 255, Default = 100},
    Callback = function(v) Settings.Color_G = v; for _, t in ipairs(ActiveTornados) do UpdateVisuals(t) end end
})

TabPhysics:Slider({
    Title = "Color: Blue",
    Value = {Min = 0, Max = 255, Default = 105},
    Callback = function(v) Settings.Color_B = v; for _, t in ipairs(ActiveTornados) do UpdateVisuals(t) end end
})

TabPhysics:Slider({
    Title = "Wander AI Speed",
    Value = {Min = 0, Max = 120, Default = 25},
    Callback = function(v) Settings.WanderSpeed = v end
})

-- =================[ Funções auxiliares ]================--

function GiveProbeTool()
    local char = Player.Character
    if not char then return end
    local backpack = Player:FindFirstChild("Backpack")
    if not backpack then return end

    local existing = backpack:FindFirstChild("Sonda")
    if existing then existing:Destroy() end

    local tool = Instance.new("Tool")
    tool.Name = "Sonda"
    tool.RequiresHandle = false
    tool.CanBeDropped = true
    tool.ManualActivationOnly = true
    tool.ToolTip = "Clique para plantar uma sonda meteorológica"

    tool.Activated:Connect(function()
        local humanoid = char:FindFirstChildOfClass("Humanoid")
        if humanoid then humanoid:UnequipTools() end
        tool.Parent = nil
        PlantProbe(char)
        tool:Destroy()
    end)

    tool.Parent = backpack
    WindUI:Notify({Title = "Sonda", Content = "Sonda adicionada ao inventário!", Duration = 3})
end

function PlantProbe(char)
    local root = char:FindFirstChild("HumanoidRootPart")
    if not root then return end

    local probe = Instance.new("Part")
    probe.Name = "Probe"
    probe.Anchored = true
    probe.CanCollide = false
    probe.Size = Vector3.new(0.5, 2, 0.5)
    probe.Material = Enum.Material.Metal
    probe.BrickColor = BrickColor.new("Bright red")
    probe.Position = root.Position + Vector3.new(0, -2, 0)
    probe.Parent = Workspace

    local light = Instance.new("PointLight", probe)
    light.Brightness = 1
    light.Range = 4
    light.Color = Color3.fromRGB(255, 100, 100)

    local sound = Instance.new("Sound", probe)
    sound.SoundId = "rbxassetid://9128805280"
    sound.Volume = 0.5
    sound:Play()

    Debris:AddItem(probe, 300)
    WindUI:Notify({Title = "Mesonet", Content = "Sonda plantada!", Duration = 3})
end

function SpawnRandomTornado()
    local efLevel = math.random(0, 6)
    local efScale = EF_SCALES[efLevel] or 10.0
    local height = math.random(670, 710)
    local maxWind = math.random(10, 525)  -- máximo 525
    local baseWidth = math.random(0.4, 16)
    local widthMult = math.random() * 0.8 + 0.8
    local mutation = ({ "Normal", "Mutant", "Corrupted", "Cursed", "Radioactive", "Firenado" })[math.random(1,6)]
    local tornadoType = ({ "Cone", "Wedge", "Rope", "Bowl", "Dust Devil" })[math.random(1,5)]
    local physicsMode = ({ "Original", "Better", "Realistic" })[math.random(1,3)]
    local lifetime = math.random(5, 200)
    local introDuration = math.random(0, 10)
    local outroDuration = math.random(0, 15)
    local wanderSpeed = math.random(0, 120)
    local spawnX = math.random(-5000, 5000)
    local spawnZ = math.random(-5000, 5000)

    -- Preserva Special atual
    local oldSpecial = Settings.Special
    local oldSettings = {
        EF_Level = Settings.EF_Level, EF_Scale = Settings.EF_Scale,
        TornadoHeight = Settings.TornadoHeight, MaxWindSpeed = Settings.MaxWindSpeed,
        BaseWidth = Settings.BaseWidth, WidthMultiplier = Settings.WidthMultiplier,
        Mutation = Settings.Mutation, TornadoType = Settings.TornadoType,
        PhysicsMode = Settings.PhysicsMode, TornadoLifetime = Settings.TornadoLifetime,
        IntroDuration = Settings.IntroDuration, OutroDuration = Settings.OutroDuration,
        WanderSpeed = Settings.WanderSpeed,
        SpawnPosX = Settings.SpawnPosX, SpawnPosZ = Settings.SpawnPosZ,
        Special = Settings.Special,
    }

    Settings.EF_Level = efLevel
    Settings.EF_Scale = efScale
    Settings.TornadoHeight = height
    Settings.MaxWindSpeed = maxWind
    Settings.BaseWidth = baseWidth
    Settings.WidthMultiplier = widthMult
    Settings.Mutation = mutation
    Settings.TornadoType = tornadoType
    Settings.PhysicsMode = physicsMode
    Settings.TornadoLifetime = lifetime
    Settings.IntroDuration = introDuration
    Settings.OutroDuration = outroDuration
    Settings.WanderSpeed = wanderSpeed
    Settings.SpawnPosX = spawnX
    Settings.SpawnPosZ = spawnZ
    -- Mantém Special como estava (não alteramos)

    local tornado = SpawnTornado(nil, nil, Vector3.new(spawnX, 5, spawnZ))

    for k, v in pairs(oldSettings) do Settings[k] = v end
    return tornado
end

function UpdateMutationEffects(tornado)
    -- Atualiza efeitos visuais e força baseado na mutação (com nomes novos)
    if Settings.TornadoType == "Dust Devil" then
        for _, data in ipairs(tornado.Segments) do
            local eye = data.Part:FindFirstChild("EyeDecal")
            if eye then eye:Destroy() end
        end
        if tornado.ExtraEmitter then
            tornado.ExtraEmitter:Destroy()
            tornado.ExtraEmitter = nil
        end
        local col = Color3.fromRGB(194, 178, 128)
        for _, data in ipairs(tornado.Segments) do
            data.Part.Color = col
            data.Part.Transparency = 0.3
        end
        tornado.MutationStrengthMult = 0.3
        return
    end

    local col
    local strengthMult = 1.0
    local extraEmitter = nil

    if tornado.ExtraEmitter then
        tornado.ExtraEmitter:Destroy()
        tornado.ExtraEmitter = nil
    end

    if Settings.Mutation == "Mutant" then
        col = Color3.fromRGB(150, 0, 0)
        strengthMult = 1.5
        for _, data in ipairs(tornado.Segments) do
            local part = data.Part
            if not part:FindFirstChild("EyeDecal") then
                local decal = Instance.new("Decal", part)
                decal.Name = "EyeDecal"
                decal.Texture = "rbxassetid://676344281"
                decal.Face = Enum.NormalId.Front
                decal.Transparency = 0.2
            end
        end
    elseif Settings.Mutation == "Corrupted" then  -- era Cursed
        col = Color3.fromRGB(0, 0, 255)
        for _, data in ipairs(tornado.Segments) do
            local eye = data.Part:FindFirstChild("EyeDecal")
            if eye then eye:Destroy() end
        end
    elseif Settings.Mutation == "Cursed" then  -- era Monster
        col = Color3.fromRGB(0, 0, 0)
        for _, data in ipairs(tornado.Segments) do
            local eye = data.Part:FindFirstChild("EyeDecal")
            if eye then eye:Destroy() end
        end
    elseif Settings.Mutation == "Radioactive" then
        col = Color3.fromRGB(0, 255, 0)
        strengthMult = 1.8
        local attach = Instance.new("Attachment", tornado.Core)
        local emitter = Instance.new("ParticleEmitter", attach)
        emitter.Texture = "rbxassetid://258128463"
        emitter.Size = NumberSequence.new(0.5, 2)
        emitter.Transparency = NumberSequence.new(0.3, 0.9)
        emitter.Speed = NumberRange.new(20, 60)
        emitter.Lifetime = NumberRange.new(1, 3)
        emitter.Rate = 100
        emitter.SpreadAngle = Vector2.new(360, 360)
        emitter.Color = ColorSequence.new(Color3.fromRGB(0,255,0))
        tornado.ExtraEmitter = emitter
    elseif Settings.Mutation == "Firenado" then
        col = Color3.fromRGB(255, 100, 0)
        strengthMult = 2.0
        local attach = Instance.new("Attachment", tornado.Core)
        local emitter = Instance.new("ParticleEmitter", attach)
        emitter.Texture = "rbxassetid://199806618"
        emitter.Size = NumberSequence.new(0.5, 3)
        emitter.Transparency = NumberSequence.new(0.2, 0.8)
        emitter.Speed = NumberRange.new(30, 80)
        emitter.Lifetime = NumberRange.new(1, 2)
        emitter.Rate = 80
        emitter.SpreadAngle = Vector2.new(180, 180)
        emitter.Color = ColorSequence.new(Color3.fromRGB(255,69,0), Color3.fromRGB(255,140,0))
        tornado.ExtraEmitter = emitter
    elseif Settings.Mutation == "Violent" then
        col = Color3.fromRGB(90, 90, 95)
        strengthMult = 3.5
        local attach = Instance.new("Attachment", tornado.Core)
        local emitter = Instance.new("ParticleEmitter", attach)
        emitter.Texture = "rbxassetid://199806618"
        emitter.Size = NumberSequence.new(0.5, 3)
        emitter.Transparency = NumberSequence.new(0.2, 0.8)
        emitter.Speed = NumberRange.new(30, 80)
        emitter.Lifetime = NumberRange.new(1, 2)
        emitter.Rate = 80
        emitter.SpreadAngle = Vector2.new(180, 180)
        emitter.Color = ColorSequence.new(Color3.fromRGB(255,69,0), Color3.fromRGB(255,140,0))
        tornado.ExtraEmitter = emitter
    else -- Normal
        col = Color3.fromRGB(Settings.Color_R, Settings.Color_G, Settings.Color_B)
        for _, data in ipairs(tornado.Segments) do
            local eye = data.Part:FindFirstChild("EyeDecal")
            if eye then eye:Destroy() end
        end
    end

    for _, data in ipairs(tornado.Segments) do
        data.Part.Color = col
        data.Part.Transparency = Settings.FunnelTrans
    end
    if tornado.DebrisEmitter then
        tornado.DebrisEmitter.Color = ColorSequence.new(col)
        tornado.DebrisEmitter.Rate = Settings.DebrisDensity
    end
    tornado.MutationStrengthMult = strengthMult
end

function UpdateVisuals(tornado)
    if Settings.TornadoType == "Dust Devil" then
        local col = Color3.fromRGB(194, 178, 128)
        for _, data in ipairs(tornado.Segments) do
            data.Part.Color = col
            data.Part.Transparency = 0.3
        end
        return
    end

    local col = Color3.fromRGB(Settings.Color_R, Settings.Color_G, Settings.Color_B)
    for _, data in ipairs(tornado.Segments) do
        data.Part.Color = col
        data.Part.Transparency = Settings.FunnelTrans
    end
    if tornado.DebrisEmitter then
        tornado.DebrisEmitter.Color = ColorSequence.new(col)
    end
end

local function GetTrailColor(effectiveWidth)
    if effectiveWidth <= 50 then
        return Color3.fromRGB(173, 216, 230)
    elseif effectiveWidth <= 110 then
        return Color3.fromRGB(255, 255, 0)
    elseif effectiveWidth <= 700 then
        return Color3.fromRGB(255, 165, 0)
    elseif effectiveWidth <= 900 then
        return Color3.fromRGB(255, 0, 0)
    else
        return Color3.fromRGB(255, 192, 203)
    end
end

function CreateTrailPart(tornado)
    if not Settings.ShowTrail then return end
    
    local effectiveTopWidth = tornado.targetFullTopWidth * tornado.currentScale
    local trailWidth = effectiveTopWidth * 0.8
    local trailColor = GetTrailColor(effectiveTopWidth)
    
    local trailPart = Instance.new("Part")
    trailPart.Name = "Trail"
    trailPart.Shape = Enum.PartType.Cylinder
    trailPart.Anchored = true
    trailPart.CanCollide = false
    trailPart.CanTouch = false
    trailPart.CanQuery = false
    trailPart.Material = Enum.Material.SmoothPlastic
    trailPart.Color = trailColor
    trailPart.Transparency = 0.5
    trailPart.Size = Vector3.new(5, trailWidth * 2, trailWidth * 2)
    trailPart.CFrame = CFrame.new(tornado.Core.Position) * CFrame.Angles(0, 0, math.rad(90))
    trailPart.Parent = Workspace
    
    table.insert(TrailParts, trailPart)
    Debris:AddItem(trailPart, 8500)
    
    if #TrailParts > 51500 then
        local oldestPart = table.remove(TrailParts, 1)
        if oldestPart then oldestPart:Destroy() end
    end
end

function CreateWindfieldPart(tornado)
    if not ShowWindfield then return end
    
    local effectiveTopWidth = tornado.targetFullTopWidth * tornado.currentScale
    local windWidth = effectiveTopWidth * 1.2
    
    local windPart = Instance.new("Part")
    windPart.Name = "Windfield"
    windPart.Shape = Enum.PartType.Cylinder
    windPart.Anchored = true
    windPart.CanCollide = false
    windPart.CanTouch = false
    windPart.CanQuery = false
    windPart.Material = Enum.Material.SmoothPlastic
    windPart.Color = Color3.fromRGB(100, 150, 255)
    windPart.Transparency = 0.7
    windPart.Size = Vector3.new(5, windWidth * 2, windWidth * 2)
    windPart.CFrame = CFrame.new(tornado.Core.Position) * CFrame.Angles(0, 0, math.rad(90))
    windPart.Parent = Workspace
    
    table.insert(WindfieldParts, windPart)
    Debris:AddItem(windPart, 600)
    
    if #WindfieldParts > 51500 then
        local oldest = table.remove(WindfieldParts, 1)
        if oldest then oldest:Destroy() end
    end
end

-- ===== NOVA FUNÇÃO PARA CRIAR VÓRTICES INTERNOS (MULTI-VORTEX) =====
-- Agora com segmentos DEITADOS
local function CreateInnerVortices(tornado)
    if not tornado or not tornado.Active then return end
    if tornado.InnerVortices then
        for _, v in ipairs(tornado.InnerVortices) do
            for _, seg in ipairs(v.Segments) do
                if seg.Part then seg.Part:Destroy() end
            end
        end
        tornado.InnerVortices = nil
    end

    local numVortices = 3
    local radius = tornado.targetFullTopWidth * tornado.currentScale * 0.15
    local height = Settings.TornadoHeight * 0.9
    local vortexHeight = height * 0.3
    local vortexWidth = tornado.baseWidth * 0.3

    local vortices = {}
    for i = 1, numVortices do
        local angle = (i-1) * (360 / numVortices)
        local segments = {}
        for j = 1, 10 do
            local ratio = j / 10
            local h = ratio * vortexHeight
            local width = vortexWidth * (0.5 + 0.5 * ratio)
            local part = Instance.new("Part")
            part.Shape = Enum.PartType.Cylinder
            part.Anchored = true
            part.CanCollide = false
            part.CanTouch = false
            part.CanQuery = false
            part.Material = Enum.Material.SmoothPlastic
            -- Segmento deitado: largura na horizontal, altura fixa pequena
            part.Size = Vector3.new(width, 2, width)
            part.Transparency = 0.4
            part.Color = Color3.fromRGB(60, 60, 60)
            part.Parent = tornado.Model

            local decal = Instance.new("Decal", part)
            decal.Texture = "rbxassetid://258128463"
            decal.Transparency = 0.6

            table.insert(segments, {Part = part, Height = h})
        end
        table.insert(vortices, {
            Segments = segments,
            Angle = angle,
            OrbitSpeed = math.random(30, 60) / 100
        })
    end
    tornado.InnerVortices = vortices
end

-- ===== NOVA FUNÇÃO PARA CRIAR VÓRTICES INTERNOS USADOS COMO SATÉLITES =====
-- (mesma lógica, mas com órbita maior e independente)
local function CreateSatelliteVortices(tornado)
    if not tornado or not tornado.Active then return end
    if tornado.SatelliteVortices then
        for _, v in ipairs(tornado.SatelliteVortices) do
            for _, seg in ipairs(v.Segments) do
                if seg.Part then seg.Part:Destroy() end
            end
        end
        tornado.SatelliteVortices = nil
    end

    local numSatellites = Settings.SatelliteCount
    if numSatellites == 0 then return end

    local orbitRadius = tornado.targetFullTopWidth * tornado.currentScale * 2.0
    local vortexHeight = Settings.TornadoHeight * 0.2
    local vortexWidth = tornado.baseWidth * 0.25

    local satellites = {}
    for i = 1, numSatellites do
        local angle = (i-1) * (360 / numSatellites) + math.random(-5, 5)
        local segments = {}
        for j = 1, 8 do
            local ratio = j / 8
            local h = ratio * vortexHeight
            local width = vortexWidth * (0.5 + 0.5 * ratio)
            local part = Instance.new("Part")
            part.Shape = Enum.PartType.Cylinder
            part.Anchored = true
            part.CanCollide = false
            part.CanTouch = false
            part.CanQuery = false
            part.Material = Enum.Material.SmoothPlastic
            part.Size = Vector3.new(width, 1.5, width)  -- deitado
            part.Transparency = 0.5
            part.Color = Color3.fromRGB(80, 80, 80)
            part.Parent = tornado.Model

            local decal = Instance.new("Decal", part)
            decal.Texture = "rbxassetid://258128463"
            decal.Transparency = 0.6

            table.insert(segments, {Part = part, Height = h})
        end
        table.insert(satellites, {
            Segments = segments,
            Angle = angle,
            OrbitSpeed = (0.2 + math.random() * 0.4),
            OrbitRadius = orbitRadius * (0.8 + math.random() * 0.4)
        })
    end
    tornado.SatelliteVortices = satellites
end

-- ===== SISTEMA DE PARTÍCULAS DE FUMAÇA (estilo JT1) =====
-- Cria um emissor que gera partículas partindo do raio do top width em direção ao centro
local function CreateSmokeParticles(tornado)
    if tornado.SmokeEmitter then
        tornado.SmokeEmitter:Destroy()
        tornado.SmokeEmitter = nil
    end

    local attach = Instance.new("Attachment", tornado.Core)
    local emitter = Instance.new("ParticleEmitter", attach)
    emitter.Texture = "rbxassetid://258128463"  -- fumaça
    emitter.Size = NumberSequence.new(1, 4)
    emitter.Transparency = NumberSequence.new(0.4, 0.9)
    emitter.Speed = NumberRange.new(5, 15)  -- velocidade baixa para criar efeito de deriva
    emitter.Lifetime = NumberRange.new(2, 5)
    emitter.Rate = 30
    emitter.SpreadAngle = Vector2.new(0, 360)
    emitter.RotSpeed = NumberRange.new(-20, 20)
    -- A posição inicial será definida manualmente no loop (usaremos VelocityInheritance e aceleração)
    -- Mas para simular partículas que vão do raio para o centro, usaremos Acceleration
    -- Além disso, usaremos um script para posicionar o emissor em anel
    -- Vamos criar partículas com aceleração centrípeta
    emitter.Acceleration = Vector3.new(0, 0, 0)  -- será aplicado via script
    emitter.VelocityInheritance = 0
    emitter.Enabled = true

    tornado.SmokeEmitter = emitter
    tornado.SmokeAttach = attach

    -- Criamos um loop para atualizar as partículas (posição inicial e aceleração)
    -- Usaremos um segundo emissor ou faremos via script
    -- Para simplificar, vamos usar um emissor que gera partículas em posições aleatórias no anel
    -- e aplicar uma força para dentro no loop Heartbeat.
    -- Vamos armazenar as partículas ativas em uma tabela.
    if tornado.SmokeParticles then
        for _, p in ipairs(tornado.SmokeParticles) do
            if p then p:Destroy() end
        end
    end
    tornado.SmokeParticles = {}
end

-- Função para atualizar as partículas de fumaça (chamada no Heartbeat)
local function UpdateSmokeParticles(tornado, dt)
    if not tornado.SmokeEmitter or not tornado.Core then return end
    local center = tornado.Core.Position
    local topWidth = tornado.targetFullTopWidth * tornado.currentScale
    local radius = topWidth * 0.8  -- raio inicial

    -- Criar novas partículas periodicamente
    if math.random() < 0.1 then  -- chance de spawn
        local angle = math.random() * 2 * math.pi
        local dist = radius * (0.5 + math.random() * 0.5)
        local x = center.X + math.cos(angle) * dist
        local z = center.Z + math.sin(angle) * dist
        local y = center.Y + math.random() * topWidth * 0.5

        local particle = Instance.new("Part")
        particle.Name = "SmokeParticle"
        particle.Anchored = false
        particle.CanCollide = false
        particle.CanTouch = false
        particle.CanQuery = false
        particle.Material = Enum.Material.SmoothPlastic
        particle.Size = Vector3.new(1, 1, 1)
        particle.BrickColor = BrickColor.new("Medium stone grey")
        particle.Transparency = 0.7
        particle.Position = Vector3.new(x, y, z)
        particle.Parent = Workspace

        -- adicionar um ponto de luz?
        local att = Instance.new("Attachment", particle)
        local pEmitter = Instance.new("ParticleEmitter", att)
        pEmitter.Texture = "rbxassetid://258128463"
        pEmitter.Size = NumberSequence.new(0.5, 2)
        pEmitter.Transparency = NumberSequence.new(0.2, 0.9)
        pEmitter.Lifetime = NumberRange.new(1, 3)
        pEmitter.Rate = 0
        pEmitter.SpreadAngle = Vector2.new(0, 0)
        pEmitter.VelocityInheritance = 0
        pEmitter.Enabled = true

        -- Armazenar para destruição posterior
        if not tornado.SmokeParticles then tornado.SmokeParticles = {} end
        table.insert(tornado.SmokeParticles, particle)

        -- Adicionar força centrípeta e rotação
        local dir = (center - particle.Position) * Vector3.new(1,0,1)
        if dir.Magnitude > 0.5 then
            dir = dir.Unit
            local tangent = Vector3.new(-dir.Z, 0, dir.X).Unit
            particle.AssemblyLinearVelocity = dir * 5 + tangent * 3 + Vector3.new(0, 1, 0)
        end

        Debris:AddItem(particle, 4)
    end

    -- Atualizar partículas existentes (força centrípeta e rotação)
    if tornado.SmokeParticles then
        for i = #tornado.SmokeParticles, 1, -1 do
            local p = tornado.SmokeParticles[i]
            if not p or not p.Parent then
                table.remove(tornado.SmokeParticles, i)
            else
                local dir = (center - p.Position) * Vector3.new(1,0,1)
                if dir.Magnitude > 0.5 then
                    local unit = dir.Unit
                    local tangent = Vector3.new(-unit.Z, 0, unit.X).Unit
                    local speed = math.max(0, 5 - dir.Magnitude * 0.1)
                    p.AssemblyLinearVelocity = p.AssemblyLinearVelocity + (unit * speed + tangent * 2) * dt
                end
                -- Transparência aumenta com o tempo
                local life = p:GetAttribute("Life") or 0
                p:SetAttribute("Life", life + dt)
                p.Transparency = math.min(0.9, 0.5 + life * 0.1)
            end
        end
    end
end

function GenerateFunnel(tornado)
    for _, data in ipairs(tornado.Segments) do
        data.Part:Destroy()
    end
    table.clear(tornado.Segments)

    local totalHeight = Settings.TornadoHeight
    local baseWidth = Settings.BaseWidth * (Settings.WidthMultiplier or 1.0)
    local targetFullTopWidth = baseWidth
    
    if Settings.TornadoType == "Dust Devil" then
        totalHeight = totalHeight * 0.4
        baseWidth = math.min(baseWidth, 8)
    elseif Settings.TornadoType == "Rope" then
        baseWidth = math.min(baseWidth, 12)
    end

    tornado.targetFullTopWidth = targetFullTopWidth
    tornado.baseWidth = baseWidth
    tornado.initialScale = math.max(0.01, 1 / targetFullTopWidth)
    tornado.shrinkTargetScale = tornado.initialScale

    local numSegments = math.floor(totalHeight / 5)
    if numSegments < 10 then numSegments = 10 end

    -- Escolhe a função de formato baseada no tipo
    local shapeFunc = SHAPE_FUNCTIONS[Settings.TornadoType] or SHAPE_FUNCTIONS["Cone"]

    for i = 1, numSegments do
        local ratio = i / numSegments
        local h = ratio * totalHeight
        local shapeMult = shapeFunc(ratio)

        local p = Instance.new("Part")
        p.Shape = Enum.PartType.Cylinder
        p.Anchored = true
        p.CanCollide = false
        p.CanTouch = false
        p.CanQuery = false
        p.Material = Enum.Material.SmoothPlastic
        p.Size = Vector3.new(5, 1, 1)
        p.Transparency = 1
        p.Parent = tornado.Model

        if Settings.TornadoType == "Dust Devil" then
            local decal = Instance.new("Decal", p)
            decal.Texture = "rbxassetid://258128463"
            decal.Transparency = 0.5
            p.Material = Enum.Material.Sand
        else
            local decal = Instance.new("Decal", p)
            decal.Texture = "rbxassetid://258128463"
            decal.Transparency = 0.5
        end

        table.insert(tornado.Segments, {
            Part = p,
            Height = h,
            Speed = 15 * (1.2 - ratio),
            ratio = ratio,
            shapeMult = shapeMult,
        })
        if i % 5 == 0 then task.wait() end
    end
end

local MAX_SEGMENT_WIDTH = 900000000000

local function UpdateFunnelSizes(tornado)
    if Settings.TornadoType == "Rope" then
        local rootPos = tornado.Core.Position
        local time = tick()
        for _, seg in ipairs(tornado.Segments) do
            local ratio = seg.ratio
            local angle = math.sin(time * 2 + ratio * 10) * 0.5
            local offsetX = math.sin(angle) * (seg.Part.Size.Y * 0.5)
            local offsetZ = math.cos(angle) * (seg.Part.Size.Y * 0.5)
            seg.Part.Position = rootPos + Vector3.new(offsetX, seg.Height, offsetZ)
            seg.Part.Size = Vector3.new(4, seg.Part.Size.Y, seg.Part.Size.Z)
        end
        return
    end

    local effectiveTopWidth = tornado.targetFullTopWidth * tornado.currentScale
    local baseWidth = tornado.baseWidth

    local topScale = tornado.currentScale
    local bottomScale = topScale
    for _, seg in ipairs(tornado.Segments) do
        local linearWidth = baseWidth * (bottomScale + (topScale - bottomScale) * seg.ratio)
        local finalWidth = linearWidth * seg.shapeMult
        finalWidth = math.min(finalWidth, MAX_SEGMENT_WIDTH)
        seg.Part.Size = Vector3.new(5, finalWidth, finalWidth)
    end
end

function SpawnTornado(mergeParent1, mergeParent2, overridePos)
    local tornado = {
        Model = Instance.new("Model", Workspace),
        Segments = {},
        Active = true,
        Core = nil,
        DebrisEmitter = nil,
        EF_Scale = Settings.EF_Scale,
        EF_Level = Settings.EF_Level,
        targetFullTopWidth = 0,
        baseWidth = 0,
        currentScale = 0.1,
        initialScale = 0,
        shrinkTargetScale = 0,
        state = "intro",
        introStartTime = tick(),
        introDuration = Settings.IntroDuration or 3,
        outroStartTime = nil,
        outroDuration = Settings.OutroDuration or 5,
        growStartTime = nil,
        stableStartTime = nil,
        outroTriggerTime = nil,
        fadeStartTime = nil,
        growDuration = Settings.GrowDuration,
        lifetime = Settings.TornadoLifetime,
        fadeDuration = 5,
        moveDirection = Vector3.new(math.random(-1,1), 0, math.random(-1,1)).Unit,
        targetDirection = nil,
        directionChangeSpeed = Settings.DirectionChangeSpeed,
        lastOcclusionChange = 0,
        occlusionInterval = 60,
        WanderSpeed = Settings.WanderSpeed,
        MutationStrengthMult = 50.0,
        MaxWindSpeed = Settings.MaxWindSpeed * (EF_SCALES[Settings.EF_Level] or 1),
        PhysicsMode = Settings.PhysicsMode,
        PullStrengthMult = Settings.PullStrengthMult,
        RadiusMult = Settings.RadiusMult,
        LiftSpeed = Settings.LiftSpeed,
        RotationSpeed = Settings.RotationSpeed,
        LastTrailFrame = 0,
        MesonetBeams = {},
        InnerVortices = nil,
        SatelliteVortices = nil,   -- NOVO: vórtices internos usados como satélites
        Satellites = {},          -- mantido para compatibilidade, mas não usado mais
        SpawnTime = os.time(),
        PendingSuck = {},
        UnanchoredParts = {},
        ProcessedAnchored = {},
        IsTwin = false,
        TwinPartner = nil,
        TwinAngle = 0,
        TwinOrbitRadius = 10,
        TwinOrbitSpeed = 1,
        TwinCommonCenter = nil,
        WarningSection = nil,
        WarningParagraph = nil,
        PlayerInDanger = false,
        ExtraEmitter = nil,
        IsRapidAutoSpawn = false,
        IsSatellite = false,
        SmokeEmitter = nil,       -- NOVO
        SmokeAttach = nil,
        SmokeParticles = {},
    }

    if mergeParent1 and mergeParent2 then
        tornado.EF_Level = math.max(mergeParent1.EF_Level, mergeParent2.EF_Level) + 1
        tornado.EF_Level = math.min(tornado.EF_Level, 6)
        tornado.EF_Scale = EF_SCALES[tornado.EF_Level] or 40
        tornado.MaxWindSpeed = mergeParent1.MaxWindSpeed + mergeParent2.MaxWindSpeed
        tornado.targetFullTopWidth = mergeParent1.targetFullTopWidth + mergeParent2.targetFullTopWidth
        tornado.baseWidth = mergeParent1.baseWidth + mergeParent2.baseWidth
        tornado.Core.Position = (mergeParent1.Core.Position + mergeParent2.Core.Position) / 2
        tornado.introDuration = 0.2
        tornado.currentScale = 0
        tornado.state = "intro"
    else
        TornadoCounter = TornadoCounter + 1
        tornado.Model.Name = "Tornado_" .. TornadoCounter
        local Core = Instance.new("Part", tornado.Model)
        Core.Anchored = true
        Core.CanCollide = false
        Core.Transparency = 1
        local pos = overridePos or Vector3.new(Settings.SpawnPosX, 5, Settings.SpawnPosZ)
        Core.Position = pos
        tornado.Core = Core
        tornado.Model.PrimaryPart = Core
    end

    local Core = tornado.Core or Instance.new("Part", tornado.Model)
    local attach = Instance.new("Attachment", Core)
    local emitter = Instance.new("ParticleEmitter", attach)
    emitter.Texture = "rbxassetid://258128463"
    emitter.Size = NumberSequence.new({NumberSequenceKeypoint.new(0, 20), NumberSequenceKeypoint.new(1, 100)})
    emitter.Transparency = NumberSequence.new(0.5, 1)
    emitter.Speed = NumberRange.new(50, 150)
    emitter.Lifetime = NumberRange.new(2, 4)
    emitter.SpreadAngle = Vector2.new(180, 180)
    emitter.Rate = Settings.DebrisDensity
    tornado.DebrisEmitter = emitter

    GenerateFunnel(tornado)
    tornado.currentScale = tornado.initialScale
    UpdateMutationEffects(tornado)

    tornado.targetDirection = tornado.moveDirection
    tornado.lastOcclusionChange = tick()

    -- ===== CRIAÇÃO DE SATÉLITES COMO VÓRTICES INTERNOS =====
    if Settings.Special.Satellites and not tornado.IsTwin and not tornado.IsSatellite then
        CreateSatelliteVortices(tornado)
    end

    -- ===== CRIAÇÃO DO EMISSOR DE FUMAÇA =====
    CreateSmokeParticles(tornado)

    table.insert(ActiveTornados, tornado)
    UpdateGlobalCloudPresence()

    local section = TabWarnings:Section({ Title = tornado.Model.Name })
    local paragraph = section:Paragraph({ Title = "", Desc = "Carregando..." })
    tornado.WarningSection = section
    tornado.WarningParagraph = paragraph

    if SpawnButton then
        SpawnButton:SetEnabled(#ActiveTornados == 0)
    end
    if TwinSpawnButton then
        TwinSpawnButton:SetEnabled(#ActiveTornados == 0)
    end
    return tornado
end

function SpawnTwinTornados()
    local pos = Vector3.new(Settings.SpawnPosX, 5, Settings.SpawnPosZ)
    local t1 = SpawnTornado(nil, nil, pos)
    local t2 = SpawnTornado(nil, nil, pos)

    local separation = (t1.targetFullTopWidth + t2.targetFullTopWidth) * 1.5
    local commonCenter = pos
    local orbitSpeed = 0.5
    t1.IsTwin = true
    t2.IsTwin = true
    t1.TwinPartner = t2
    t2.TwinPartner = t1
    t1.TwinCommonCenter = commonCenter
    t2.TwinCommonCenter = commonCenter
    t1.TwinOrbitRadius = separation / 35
    t2.TwinOrbitRadius = separation / 35
    t1.TwinOrbitSpeed = orbitSpeed
    t2.TwinOrbitSpeed = orbitSpeed
    t1.TwinAngle = 0
    t2.TwinAngle = math.pi

    t1.Core.Position = commonCenter + Vector3.new(math.cos(t1.TwinAngle) * t1.TwinOrbitRadius, 0, math.sin(t1.TwinAngle) * t1.TwinOrbitRadius)
    t2.Core.Position = commonCenter + Vector3.new(math.cos(t2.TwinAngle) * t2.TwinOrbitRadius, 0, math.sin(t2.TwinAngle) * t2.TwinOrbitRadius)

    t1.WanderSpeed = 0
    t2.WanderSpeed = 0
    t1.targetDirection = nil
    t2.targetDirection = nil
end

local function DestroyTornado(tornado)
    tornado.Active = false

    if tornado.Satellites then
        for _, sat in ipairs(tornado.Satellites) do
            if sat and sat.Active then
                DestroyTornado(sat)
            end
        end
        tornado.Satellites = {}
    end

    -- Destroi os novos satélites (vórtices internos)
    if tornado.SatelliteVortices then
        for _, v in ipairs(tornado.SatelliteVortices) do
            for _, seg in ipairs(v.Segments) do
                if seg.Part then seg.Part:Destroy() end
            end
        end
        tornado.SatelliteVortices = nil
    end

    if tornado.MesonetBeams then
        for _, beam in ipairs(tornado.MesonetBeams) do beam:Destroy() end
    end
    if tornado.InnerVortices then
        for _, v in ipairs(tornado.InnerVortices) do
            for _, seg in ipairs(v.Segments) do
                if seg.Part then seg.Part:Destroy() end
            end
        end
        tornado.InnerVortices = nil
    end
    if tornado.ExtraEmitter then
        tornado.ExtraEmitter:Destroy()
    end
    if tornado.SmokeEmitter then
        tornado.SmokeEmitter:Destroy()
        tornado.SmokeEmitter = nil
    end
    if tornado.SmokeParticles then
        for _, p in ipairs(tornado.SmokeParticles) do
            if p then p:Destroy() end
        end
        tornado.SmokeParticles = {}
    end
    if tornado.Model then
        tornado.Model:Destroy()
    end
    if tornado.WarningSection then
        tornado.WarningSection:Destroy()
        tornado.WarningSection = nil
        tornado.WarningParagraph = nil
    end
    for i, t in ipairs(ActiveTornados) do
        if t == tornado then
            table.remove(ActiveTornados, i)
            break
        end
    end
    if tornado.IsTwin and tornado.TwinPartner then
        local partner = tornado.TwinPartner
        partner.IsTwin = false
        partner.TwinPartner = nil
        partner.WanderSpeed = Settings.WanderSpeed
    end
    if tornado.IsRapidAutoSpawn then
        for i, t in ipairs(RapidAutoSpawnTornados) do
            if t == tornado then
                table.remove(RapidAutoSpawnTornados, i)
                break
            end
        end
    end
    
    if SupercellGridEnabled and #ActiveTornados == 0 then
        SupercellFogActive = false
        if not isNearTornado then
            Lighting.FogStart = originalFogStart
            Lighting.FogEnd = originalFogEnd
            Lighting.FogColor = originalFogColor
        end
    end

    UpdateGlobalCloudPresence()

    if #ActiveTornados == 0 then
        if SpawnButton then SpawnButton:SetEnabled(true) end
        if TwinSpawnButton then TwinSpawnButton:SetEnabled(true) end
    end
end

function DestroyAllTornados()
    for _, t in ipairs(ActiveTornados) do
        t.Active = false
        if t.Satellites then
            for _, sat in ipairs(t.Satellites) do
                if sat and sat.Active then DestroyTornado(sat) end
            end
            t.Satellites = {}
        end
        if t.SatelliteVortices then
            for _, v in ipairs(t.SatelliteVortices) do
                for _, seg in ipairs(v.Segments) do
                    if seg.Part then seg.Part:Destroy() end
                end
            end
            t.SatelliteVortices = nil
        end
        if t.MesonetBeams then
            for _, beam in ipairs(t.MesonetBeams) do beam:Destroy() end
        end
        if t.InnerVortices then
            for _, v in ipairs(t.InnerVortices) do
                for _, seg in ipairs(v.Segments) do
                    if seg.Part then seg.Part:Destroy() end
                end
            end
            t.InnerVortices = nil
        end
        if t.ExtraEmitter then
            t.ExtraEmitter:Destroy()
        end
        if t.SmokeEmitter then
            t.SmokeEmitter:Destroy()
        end
        if t.SmokeParticles then
            for _, p in ipairs(t.SmokeParticles) do
                if p then p:Destroy() end
            end
            t.SmokeParticles = {}
        end
        if t.WarningSection then
            t.WarningSection:Destroy()
            t.WarningSection = nil
            t.WarningParagraph = nil
        end
        if t.Model then
            t.Model:Destroy()
        end
    end
    table.clear(ActiveTornados)
    table.clear(RapidAutoSpawnTornados)
    
    SupercellFogActive = false
    if not isNearTornado then
        Lighting.FogStart = originalFogStart
        Lighting.FogEnd = originalFogEnd
        Lighting.FogColor = originalFogColor
    end

    UpdateGlobalCloudPresence()
    
    for _, part in ipairs(TrailParts) do
        if part then part:Destroy() end
    end
    table.clear(TrailParts)
    
    for _, part in ipairs(WindfieldParts) do
        if part then part:Destroy() end
    end
    table.clear(WindfieldParts)

    if SpawnButton then SpawnButton:SetEnabled(true) end
    if TwinSpawnButton then TwinSpawnButton:SetEnabled(true) end
end

local function GetNearbyProbeCount(tornado)
    local count = 0
    local radius = tornado.targetFullTopWidth * tornado.currentScale * 2
    for _, obj in ipairs(Workspace:GetChildren()) do
        if obj.Name == "Probe" and obj:IsA("BasePart") then
            local dist = (obj.Position - tornado.Core.Position).Magnitude
            if dist <= radius then
                count = count + 1
            end
        end
    end
    return count
end

local function UpdateMesonetVisuals(tornado)
    if not Settings.ShowMesonet then
        if tornado.MesonetBeams then
            for _, beam in ipairs(tornado.MesonetBeams) do beam:Destroy() end
            tornado.MesonetBeams = {}
        end
        return
    end

    if tornado.MesonetBeams then
        for _, beam in ipairs(tornado.MesonetBeams) do beam:Destroy() end
        tornado.MesonetBeams = {}
    end

    local corePos = tornado.Core.Position
    local radius = tornado.targetFullTopWidth * tornado.currentScale * 2
    for _, obj in ipairs(Workspace:GetChildren()) do
        if obj.Name == "Probe" and obj:IsA("BasePart") then
            local dist = (obj.Position - corePos).Magnitude
            if dist <= radius then
                local beam = Instance.new("Beam")
                beam.Attachment0 = Instance.new("Attachment", tornado.Core)
                beam.Attachment0.Position = Vector3.new(0,0,0)
                beam.Attachment1 = Instance.new("Attachment", obj)
                beam.Color = ColorSequence.new(Color3.fromRGB(0,255,255))
                beam.Width0 = 0.2
                beam.Width1 = 0.2
                beam.Transparency = NumberSequence.new(0.5)
                beam.Parent = tornado.Core
                table.insert(tornado.MesonetBeams, beam)
            end
        end
    end
end

-- =================[ HUD ]================
local HudFrame

local function CreateHUD()
    if HudFrame then return end
    local sg = Instance.new("ScreenGui")
    sg.Name = "TornadoHUD"
    sg.Parent = game:GetService("CoreGui")
    local frame = Instance.new("Frame", sg)
    frame.Size = UDim2.new(0, 200, 0, 110)
    frame.Position = UDim2.new(1, -210, 0, 10)
    frame.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
    frame.BackgroundTransparency = 0.6
    frame.BorderSizePixel = 0
    local layout = Instance.new("UIListLayout", frame)
    layout.Padding = UDim.new(0, 4)
    layout.HorizontalAlignment = Enum.HorizontalAlignment.Center
    layout.VerticalAlignment = Enum.VerticalAlignment.Center

    local text1 = Instance.new("TextLabel", frame)
    text1.Size = UDim2.new(1, -10, 0, 25)
    text1.Text = "Mesonet: 0"
    text1.TextColor3 = Color3.fromRGB(255, 255, 255)
    text1.BackgroundTransparency = 1

    local textDist = Instance.new("TextLabel", frame)
    textDist.Size = UDim2.new(1, -10, 0, 25)
    textDist.Text = "Distância: 0"
    textDist.TextColor3 = Color3.fromRGB(255, 255, 255)
    textDist.BackgroundTransparency = 1

    local text2 = Instance.new("TextLabel", frame)
    text2.Size = UDim2.new(1, -10, 0, 25)
    text2.Text = "EF: EF0"
    text2.TextColor3 = Color3.fromRGB(255, 255, 255)
    text2.BackgroundTransparency = 1

    local text3 = Instance.new("TextLabel", frame)
    text3.Size = UDim2.new(1, -10, 0, 25)
    text3.Text = "Windspeed: 0"
    text3.TextColor3 = Color3.fromRGB(255, 255, 255)
    text3.BackgroundTransparency = 1

    HudFrame = sg
    HudFrame.Visible = Settings.ShowHUD
end

-- 🌫️ Controle do fog (agora usando raio do funil)
local function UpdateFogEffect()
    local char = Player.Character
    local root = char and char:FindFirstChild("HumanoidRootPart")
    if not root then
        if isNearTornado then
            Lighting.FogStart = originalFogStart
            Lighting.FogEnd = originalFogEnd
            Lighting.FogColor = originalFogColor
            isNearTornado = false
        end
        return
    end

    local newNear = false
    for _, tornado in ipairs(ActiveTornados) do
        if tornado.Active and tornado.state ~= "dead" then
            local effectiveTopWidth = tornado.targetFullTopWidth * tornado.currentScale
            -- Fog só ativa dentro do funil (0.4 do top width)
            local fogRadius = effectiveTopWidth * 0.4
            local dist = (tornado.Core.Position - root.Position).Magnitude
            if dist <= fogRadius then
                newNear = true
                break
            end
        end
    end

    if newNear and not isNearTornado then
        Lighting.FogStart = 40
        Lighting.FogEnd = 100
        Lighting.FogColor = Color3.fromRGB(50, 50, 50)
        isNearTornado = true
    elseif not newNear and isNearTornado then
        if SupercellGridEnabled and SupercellFogActive then
            Lighting.FogStart = 80
            Lighting.FogEnd = 250
            Lighting.FogColor = Color3.fromRGB(180, 190, 200)
        else
            Lighting.FogStart = originalFogStart
            Lighting.FogEnd = originalFogEnd
            Lighting.FogColor = originalFogColor
        end
        isNearTornado = false
    end
end

local function UpdateSupercellFog()
    if SupercellGridEnabled and SupercellFogActive and not isNearTornado then
        Lighting.FogStart = 80
        Lighting.FogEnd = 250
        Lighting.FogColor = Color3.fromRGB(180, 190, 200)
    elseif not SupercellGridEnabled and not isNearTornado then
        Lighting.FogStart = originalFogStart
        Lighting.FogEnd = originalFogEnd
        Lighting.FogColor = originalFogColor
    end
end

-- =================[ REDIRECIONAR ]================
function RedirectTornadosToPlayer()
    local char = Player.Character
    local root = char and char:FindFirstChild("HumanoidRootPart")
    if not root then
        WindUI:Notify({Title = "Erro", Content = "Personagem não encontrado!", Duration = 3, Icon = "alert"})
        return
    end
    local playerPos = root.Position
    local count = 0
    for _, tornado in ipairs(ActiveTornados) do
        if tornado.Active and tornado.state ~= "dead" and tornado.state ~= "outro" and tornado.state ~= "merging" then
            if tornado.IsTwin then
                tornado.IsTwin = false
                if tornado.TwinPartner then
                    tornado.TwinPartner.IsTwin = false
                    tornado.TwinPartner.TwinPartner = nil
                    tornado.TwinPartner.WanderSpeed = Settings.WanderSpeed
                end
                tornado.TwinPartner = nil
                tornado.WanderSpeed = Settings.WanderSpeed
                tornado.TwinCommonCenter = nil
            end
            local toPlayer = playerPos - tornado.Core.Position
            if toPlayer.Magnitude > 0.1 then
                tornado.targetDirection = toPlayer.Unit
                tornado.moveDirection = toPlayer.Unit
                tornado.lastOcclusionChange = tick()
                count = count + 1
            end
        end
    end
    if count > 0 then
        WindUI:Notify({Title = "Redirecionando", Content = string.format("%d tornados estão perseguindo você!", count), Duration = 3, Icon = "target"})
    else
        WindUI:Notify({Title = "Redirecionar", Content = "Nenhum tornado ativo para redirecionar.", Duration = 3, Icon = "info"})
    end
end

function RedirectTornadosToSpawn()
    local spawnPos = Vector3.new(Settings.SpawnPosX, 5, Settings.SpawnPosZ)
    local count = 0
    for _, tornado in ipairs(ActiveTornados) do
        if tornado.Active and tornado.state ~= "dead" and tornado.state ~= "outro" and tornado.state ~= "merging" then
            if tornado.IsTwin then
                tornado.IsTwin = false
                if tornado.TwinPartner then
                    tornado.TwinPartner.IsTwin = false
                    tornado.TwinPartner.TwinPartner = nil
                    tornado.TwinPartner.WanderSpeed = Settings.WanderSpeed
                end
                tornado.TwinPartner = nil
                tornado.WanderSpeed = Settings.WanderSpeed
                tornado.TwinCommonCenter = nil
            end
            local toSpawn = spawnPos - tornado.Core.Position
            if toSpawn.Magnitude > 0.1 then
                tornado.targetDirection = toSpawn.Unit
                tornado.moveDirection = toSpawn.Unit
                tornado.lastOcclusionChange = tick()
                count = count + 1
            end
        end
    end
    if count > 0 then
        WindUI:Notify({Title = "Redirecionando", Content = string.format("%d tornados estão indo para o spawn!", count), Duration = 3})
    else
        WindUI:Notify({Title = "Redirecionar", Content = "Nenhum tornado ativo para redirecionar.", Duration = 3})
    end
end

-- =================[ SUPER CÉLULA ]================
function CreateSupercellGrid()
    RemoveSupercellGrid()
    local gridSize = 128
    local blockSize = 300
    local height = 660
    local startX = -gridSize/2 * blockSize
    local startZ = -gridSize/2 * blockSize

    for x = 0, gridSize-1 do
        for z = 0, gridSize-1 do
            local block = Instance.new("Part")
            block.Name = "SupercellBlock"
            block.Anchored = true
            block.CanCollide = false
            block.Size = Vector3.new(blockSize, 20, blockSize)
            block.Position = Vector3.new(startX + x*blockSize + blockSize/2, height, startZ + z*blockSize + blockSize/2)
            block.Color = Color3.fromRGB(128, 128, 128)
            block.Material = Enum.Material.SmoothPlastic
            block.Transparency = 0.0
            block.Parent = Workspace
            table.insert(SupercellBlocks, block)
        end
    end
end

function RemoveSupercellGrid()
    for _, block in ipairs(SupercellBlocks) do
        if block then block:Destroy() end
    end
    table.clear(SupercellBlocks)
end

-- =================[ FUNÇÕES DE PRÉ-VISUALIZAÇÃO ]================
local function CreatePreview()
    DestroyPreview()
    if not Settings.ShowPreview then return end

    -- Modelo do preview
    PreviewModel = Instance.new("Model", Workspace)
    PreviewModel.Name = "TornadoPreview"

    local totalHeight = Settings.TornadoHeight
    local baseWidth = Settings.BaseWidth * (Settings.WidthMultiplier or 1.0)
    local targetFullTopWidth = baseWidth
    if Settings.TornadoType == "Dust Devil" then
        totalHeight = totalHeight * 0.4
        baseWidth = math.min(baseWidth, 8)
    elseif Settings.TornadoType == "Rope" then
        baseWidth = math.min(baseWidth, 12)
    end

    local numSegments = math.floor(totalHeight / 5)
    if numSegments < 10 then numSegments = 10 end
    local shapeFunc = SHAPE_FUNCTIONS[Settings.TornadoType] or SHAPE_FUNCTIONS["Cone"]

    local pos = Vector3.new(Settings.SpawnPosX, 5, Settings.SpawnPosZ)

    for i = 1, numSegments do
        local ratio = i / numSegments
        local h = ratio * totalHeight
        local shapeMult = shapeFunc(ratio)
        local linearWidth = baseWidth * shapeMult
        local finalWidth = math.min(linearWidth, MAX_SEGMENT_WIDTH)

        -- Parte interna (verde transparente)
        local inner = Instance.new("Part")
        inner.Name = "PreviewInner"
        inner.Shape = Enum.PartType.Cylinder
        inner.Anchored = true
        inner.CanCollide = false
        inner.CanTouch = false
        inner.CanQuery = false
        inner.Material = Enum.Material.SmoothPlastic
        inner.Size = Vector3.new(5, finalWidth, finalWidth)
        inner.Color = Color3.fromRGB(0, 255, 0)
        inner.Transparency = 0.6
        inner.Position = pos + Vector3.new(0, h, 0)
        inner.CFrame = inner.CFrame * CFrame.Angles(0, 0, math.rad(90))
        inner.Parent = PreviewModel

        -- Outline (branco, ligeiramente maior)
        local outline = Instance.new("Part")
        outline.Name = "PreviewOutline"
        outline.Shape = Enum.PartType.Cylinder
        outline.Anchored = true
        outline.CanCollide = false
        outline.CanTouch = false
        outline.CanQuery = false
        outline.Material = Enum.Material.SmoothPlastic
        local outlineWidth = finalWidth + 0.4
        outline.Size = Vector3.new(5, outlineWidth, outlineWidth)
        outline.Color = Color3.fromRGB(255, 255, 255)
        outline.Transparency = 0.3
        outline.Position = pos + Vector3.new(0, h, 0)
        outline.CFrame = outline.CFrame * CFrame.Angles(0, 0, math.rad(90))
        outline.Parent = PreviewModel

        table.insert(PreviewSegments, {Inner = inner, Outline = outline})
    end

    -- Billboard com informações
    local sg = Instance.new("ScreenGui")
    sg.Name = "PreviewInfoGUI"
    sg.Parent = game:GetService("CoreGui")

    local mainFrame = Instance.new("Frame", sg)
    mainFrame.Size = UDim2.new(0, 260, 0, 300)
    mainFrame.Position = UDim2.new(1, -280, 0.5, -150)
    mainFrame.BackgroundColor3 = Color3.fromRGB(20, 20, 30)
    mainFrame.BackgroundTransparency = 0.2
    mainFrame.BorderSizePixel = 0

    local scroll = Instance.new("ScrollingFrame", mainFrame)
    scroll.Size = UDim2.new(1, -10, 1, -10)
    scroll.Position = UDim2.new(0, 5, 0, 5)
    scroll.BackgroundTransparency = 1
    scroll.ScrollBarThickness = 6
    scroll.CanvasSize = UDim2.new(0, 0, 0, 0)

    local layout = Instance.new("UIListLayout", scroll)
    layout.Padding = UDim.new(0, 6)
    layout.SortOrder = Enum.SortOrder.LayoutOrder

    local function AddLabel(text, color)
        local lbl = Instance.new("TextLabel", scroll)
        lbl.Size = UDim2.new(1, 0, 0, 28)
        lbl.BackgroundTransparency = 1
        lbl.Text = text
        lbl.TextColor3 = color or Color3.fromRGB(255,255,255)
        lbl.TextSize = 16
        lbl.TextXAlignment = Enum.TextXAlignment.Left
        lbl.Font = Enum.Font.GothamBold
        lbl.TextScaled = false
        return lbl
    end

    -- Preencher informações
    local info = {
        {text = "📋 Tornado Preview", color = Color3.fromRGB(200, 200, 255)},
        {text = string.format("EF: %d", Settings.EF_Level), color = Color3.fromRGB(255, 200, 100)},
        {text = string.format("Tipo: %s", Settings.TornadoType), color = Color3.fromRGB(150, 255, 150)},
        {text = string.format("Mutação: %s", Settings.Mutation), color = Color3.fromRGB(255, 150, 255)},
        {text = string.format("Windspeed: %.1f Mph/h", Settings.MaxWindSpeed), color = Color3.fromRGB(100, 200, 255)},
        {text = string.format("Largura máxima: %.1f studs", Settings.BaseWidth * Settings.WidthMultiplier), color = Color3.fromRGB(255, 200, 150)},
        {text = string.format("Altura: %d studs", Settings.TornadoHeight), color = Color3.fromRGB(200, 200, 200)},
        {text = string.format("Specials: %s", (Settings.Special.MultiVortex and "Multi-Vortex " or "") .. (Settings.Special.Satellites and "Satellites" or "")), color = Color3.fromRGB(255, 255, 100)},
        {text = string.format("Satélites: %d", Settings.SatelliteCount), color = Color3.fromRGB(100, 255, 200)},
    }
    for _, item in ipairs(info) do
        AddLabel(item.text, item.color)
    end

    -- Ajustar canvas
    local function updateCanvas()
        local totalHeight = 0
        for _, child in ipairs(scroll:GetChildren()) do
            if child:IsA("TextLabel") then
                totalHeight = totalHeight + child.Size.Y.Offset + layout.Padding.Offset
            end
        end
        scroll.CanvasSize = UDim2.new(0, 0, 0, totalHeight)
    end
    task.spawn(function()
        wait(0.1)
        updateCanvas()
    end)

    PreviewInfoGUI = sg
end

local function DestroyPreview()
    if PreviewModel then
        PreviewModel:Destroy()
        PreviewModel = nil
    end
    if PreviewInfoGUI then
        PreviewInfoGUI:Destroy()
        PreviewInfoGUI = nil
    end
    PreviewSegments = {}
end

-- =================[ Loop principal ]================--
local frameCount = 0
local lastDamageTime = {}
local lastWarningUpdate = 0

RunService.Heartbeat:Connect(function(dt)
    CleanupPartData()
    frameCount = frameCount + 1
    local char = Player.Character
    local root = char and char:FindFirstChild("HumanoidRootPart")
    local humanoid = char and char:FindFirstChild("Humanoid")
    local camera = workspace.CurrentCamera
    
    if not HudFrame then CreateHUD() end
    
    if tick() - lastWarningUpdate > 2 then
        UpdateWarnings()
        lastWarningUpdate = tick()
    end

    UpdateSupercellFog()

    -- Atualização de gêmeos
    for _, tornado in ipairs(ActiveTornados) do
        if tornado.IsTwin and tornado.Active then
            tornado.TwinAngle = tornado.TwinAngle + tornado.TwinOrbitSpeed * dt
            local cx, cz = tornado.TwinCommonCenter.X, tornado.TwinCommonCenter.Z
            local nx = cx + math.cos(tornado.TwinAngle) * tornado.TwinOrbitRadius
            local nz = cz + math.sin(tornado.TwinAngle) * tornado.TwinOrbitRadius
            tornado.Core.Position = Vector3.new(nx, tornado.Core.Position.Y, nz)
            tornado.moveDirection = Vector3.new(-math.sin(tornado.TwinAngle), 0, math.cos(tornado.TwinAngle)).Unit
            tornado.targetDirection = nil
        end
    end

    -- Fusão de tornados (IGNORA SATÉLITES)
    for i = 1, #ActiveTornados do
        local t1 = ActiveTornados[i]
        if not t1.Active or t1.IsTwin or t1.state == "merging" or t1.state == "dead" or t1.IsSatellite then continue end
        for j = i+1, #ActiveTornados do
            local t2 = ActiveTornados[j]
            if not t2.Active or t2.IsTwin or t2.state == "merging" or t2.state == "dead" or t2.IsSatellite then continue end
            local dist = (t1.Core.Position - t2.Core.Position).Magnitude
            local threshold = (t1.targetFullTopWidth * t1.currentScale + t2.targetFullTopWidth * t2.currentScale) * 0.8
            if dist < threshold then
                t1.state = "merging"
                t2.state = "merging"
                t1.fadeStartTime = tick()
                t2.fadeStartTime = tick()
                t1.fadeDuration = 0.4
                t2.fadeDuration = 0.4
                delay(0.4, function()
                    SpawnTornado(t1, t2)
                end)
            end
        end
    end

    local nearestTornado = nil
    local minDist = math.huge

    for _, tornado in ipairs(ActiveTornados) do
        if not tornado.Active then continue end

        local now = tick()
        local distToPlayer = root and (tornado.Core.Position - root.Position).Magnitude or math.huge

        if distToPlayer < minDist then
            minDist = distToPlayer
            nearestTornado = tornado
        end

        -- Mutant: persegue jogador (exceto satélites)
        if Settings.Mutation == "Mutant" and not tornado.IsTwin and not tornado.IsSatellite then
            local nearestRoot = nil
            local minDistPlr = math.huge
            for _, plr in ipairs(Players:GetPlayers()) do
                if plr.Character and plr.Character:FindFirstChild("HumanoidRootPart") then
                    local r = plr.Character.HumanoidRootPart
                    local d = (r.Position - tornado.Core.Position).Magnitude
                    if d < minDistPlr then
                        minDistPlr = d
                        nearestRoot = r
                    end
                end
            end
            if nearestRoot then
                local toPlayer = (nearestRoot.Position - tornado.Core.Position) * Vector3.new(1,0,1)
                if toPlayer.Magnitude > 0.1 then
                    tornado.targetDirection = toPlayer.Unit
                end
            end
        end

        -- Estados do tornado
        if tornado.state == "intro" then
            local elapsed = now - tornado.introStartTime
            local progress = math.clamp(elapsed / tornado.introDuration, 0, 1)
            tornado.currentScale = tornado.initialScale + (1 - tornado.initialScale) * progress
            UpdateFunnelSizes(tornado)
            for _, seg in ipairs(tornado.Segments) do
                seg.Part.Transparency = progress < (1 - seg.ratio) and 1 or 0
            end
            if progress >= 1 then
                tornado.state = "stable"
                tornado.stableStartTime = now
                tornado.outroTriggerTime = now + tornado.lifetime
            end
        elseif tornado.state == "outro" then
            local elapsed = now - tornado.outroStartTime
            local progress = math.clamp(elapsed / tornado.outroDuration, 0, 1)
            for _, seg in ipairs(tornado.Segments) do
                seg.Part.Transparency = progress >= seg.ratio and 1 or 0
            end
            if progress >= 1 then
                tornado.state = "dead"
            end
        elseif tornado.state == "stable" then
            UpdateFunnelSizes(tornado)
            if now >= tornado.outroTriggerTime then
                tornado.state = "outro"
                tornado.outroStartTime = now
            end
        elseif tornado.state == "merging" then
            local elapsed = now - tornado.fadeStartTime
            local progress = math.clamp(elapsed / 0.4, 0, 1)
            for _, seg in ipairs(tornado.Segments) do
                seg.Part.Transparency = progress
            end
            if progress >= 1 then
                tornado.state = "dead"
            end
        elseif tornado.state == "dead" then
            DestroyTornado(tornado)
            continue
        end

        -- Movimento (direção) para tornados não gêmeos
        if tornado.state ~= "dead" and tornado.state ~= "outro" and tornado.state ~= "merging" and not tornado.IsTwin then
            -- Se Multi-Vortex ativo e estável, comportamento errático (muda direção mais rápido)
            if Settings.Special.MultiVortex and tornado.state == "stable" and not tornado.IsSatellite then
                tornado.occlusionInterval = math.random(10, 25)  -- mais errático
                -- Força extra (multiplica MutationStrengthMult por 1.5)
                tornado.MutationStrengthMult = 50.0 * 1.5
            else
                -- Restaura força base se não estiver em Multi-Vortex
                if not tornado.IsSatellite then
                    tornado.MutationStrengthMult = 50.0  -- base
                end
            end

            if Settings.Mutation ~= "Mutant" then
                if not tornado.targetDirection or (now - tornado.lastOcclusionChange) > tornado.occlusionInterval then
                    local currentDir = tornado.moveDirection
                    local maxAngleRad = math.rad(Settings.OcclusionAngle)
                    local deviation = (math.random() * 2 - 1) * maxAngleRad
                    local newDir = CFrame.Angles(0, deviation, 0) * currentDir
                    tornado.targetDirection = newDir.Unit
                    tornado.lastOcclusionChange = now
                    tornado.occlusionInterval = math.random(20, 60)
                end
            end

            local angleDiff = math.acos(math.clamp(tornado.moveDirection:Dot(tornado.targetDirection), -1, 1))
            if angleDiff > 0.01 then
                local rotAxis = tornado.moveDirection:Cross(tornado.targetDirection).Unit
                if rotAxis.Magnitude > 0 then
                    local maxAngle = tornado.directionChangeSpeed * dt
                    local angle = math.min(angleDiff, maxAngle)
                    tornado.moveDirection = CFrame.fromAxisAngle(rotAxis, angle) * tornado.moveDirection
                else
                    tornado.moveDirection = tornado.targetDirection
                end
            end

            local newPos = tornado.Core.Position + tornado.moveDirection * tornado.WanderSpeed * dt
            tornado.Core.Position = newPos
        end

        -- ===== ATUALIZAÇÃO DE INNER VORTICES (MULTI-VORTEX) =====
        if Settings.Special.MultiVortex and not tornado.IsSatellite and tornado.state == "stable" then
            if not tornado.InnerVortices then
                CreateInnerVortices(tornado)
            else
                local center = tornado.Core.Position
                local radius = tornado.targetFullTopWidth * tornado.currentScale * 0.15
                for _, vortex in ipairs(tornado.InnerVortices) do
                    vortex.Angle = vortex.Angle + vortex.OrbitSpeed * dt
                    local rad = math.rad(vortex.Angle)
                    local offset = Vector3.new(math.cos(rad), 0, math.sin(rad)) * radius
                    for _, seg in ipairs(vortex.Segments) do
                        local pos = center + offset + Vector3.new(0, seg.Height, 0)
                        seg.Part.Position = pos
                        -- Segmento deitado: manter Size, mas aplicar rotação para ficar horizontal
                        seg.Part.CFrame = CFrame.new(pos) * CFrame.Angles(math.rad(90), 0, 0)
                    end
                end
            end
        elseif tornado.InnerVortices and (not Settings.Special.MultiVortex or tornado.state ~= "stable") then
            -- Limpa InnerVortices se a opção for desativada ou tornado não está estável
            for _, v in ipairs(tornado.InnerVortices) do
                for _, seg in ipairs(v.Segments) do
                    if seg.Part then seg.Part:Destroy() end
                end
            end
            tornado.InnerVortices = nil
        end

        -- ===== ATUALIZAÇÃO DE SATÉLITES (VÓRTICES INTERNOS ORBITANDO) =====
        if tornado.SatelliteVortices and tornado.state == "stable" and not tornado.IsTwin then
            local center = tornado.Core.Position
            for _, sat in ipairs(tornado.SatelliteVortices) do
                sat.Angle = sat.Angle + sat.OrbitSpeed * dt
                local rad = math.rad(sat.Angle)
                local radius = sat.OrbitRadius * tornado.currentScale
                local offset = Vector3.new(math.cos(rad), 0, math.sin(rad)) * radius
                for _, seg in ipairs(sat.Segments) do
                    local pos = center + offset + Vector3.new(0, seg.Height, 0)
                    seg.Part.Position = pos
                    seg.Part.CFrame = CFrame.new(pos) * CFrame.Angles(math.rad(90), 0, 0)
                end
            end
        end

        -- ===== ATUALIZAÇÃO DAS PARTÍCULAS DE FUMAÇA =====
        UpdateSmokeParticles(tornado, dt)

        -- ===== TRANSPARÊNCIA DINÂMICA DOS SEGMENTOS =====
        if root and tornado.state == "stable" then
            local playerPos = root.Position
            local center = tornado.Core.Position
            local topWidth = tornado.targetFullTopWidth * tornado.currentScale
            local innerRadius = topWidth * 0.2  -- raio para transparência
            for _, seg in ipairs(tornado.Segments) do
                local segPos = seg.Part.Position
                local distToPlayer = (segPos - playerPos).Magnitude
                local baseTrans = Settings.FunnelTrans
                if distToPlayer < innerRadius then
                    local factor = 1 - (distToPlayer / innerRadius)
                    local newTrans = math.min(1, baseTrans + factor * 0.5) -- fica mais transparente
                    seg.Part.Transparency = newTrans
                else
                    seg.Part.Transparency = baseTrans
                end
            end
        end

        -- Trail e Windfield
        local t = tick()
        local rootPos = tornado.Core.Position
        
        if Settings.ShowTrail and tornado.state ~= "dead" and tornado.state ~= "outro" and tornado.state ~= "merging" then
            if frameCount % 2 == 0 then
                CreateTrailPart(tornado)
            end
        end
        
        if Settings.ShowWindfield and tornado.state ~= "dead" and tornado.state ~= "outro" and tornado.state ~= "merging" then
            if frameCount % 4 == 0 then
                CreateWindfieldPart(tornado)
            end
        end
        
        UpdateMesonetVisuals(tornado)

        -- Atualiza posição dos segmentos do funil principal
        for _, seg in ipairs(tornado.Segments) do
            local h = seg.Height
            local rot = CFrame.Angles(0, t * seg.Speed * Settings.RotationSpeed * 0.7, 0)
            local wobble = Vector3.new(math.sin(t*3 + h)*3, 0, math.cos(t*3 + h)*3)
            seg.Part.CFrame = CFrame.new(rootPos + Vector3.new(0, h, 0) + wobble) * rot * CFrame.Angles(0, 0, math.rad(90))
        end

        -- Física e puxão (sem alterações)
        if tornado.state ~= "dead" and tornado.state ~= "outro" and tornado.state ~= "merging" then
            local totalHeight = Settings.TornadoHeight
            local effectiveTopWidth = tornado.targetFullTopWidth * tornado.currentScale
            local searchRadius = effectiveTopWidth * 2.5 * Settings.RadiusMult

            local probeCount = GetNearbyProbeCount(tornado)
            local baseStrength = tornado.MaxWindSpeed * tornado.MutationStrengthMult
            local strength = baseStrength * (1 + probeCount * 0.2)

            -- Dano ao jogador
            if root and humanoid then
                local dist = (root.Position - rootPos).Magnitude
                local damageRadius = effectiveTopWidth * 1.5
                if dist <= damageRadius then
                    local last = lastDamageTime[tornado] or 0
                    if tick() - last >= 2 then
                        humanoid:TakeDamage(1)
                        lastDamageTime[tornado] = tick()
                        if humanoid.Health > 0 then
                            local bp = Instance.new("BillboardGui", root)
                            bp.Adornee = root
                            bp.Size = UDim2.new(0, 50, 0, 30)
                            bp.StudsOffset = Vector3.new(0, 3, 0)
                            local label = Instance.new("TextLabel", bp)
                            label.Size = UDim2.new(1, 0, 1, 0)
                            label.BackgroundTransparency = 1
                            label.TextColor3 = Color3.fromRGB(255, 50, 50)
                            label.Text = "-1 HP"
                            label.TextScaled = true
                            label.Font = Enum.Font.Bold
                            Debris:AddItem(bp, 1)
                        end
                    end
                end
            end

            -- Puxar jogador com stun
            if root and humanoid and tornado.state ~= "dead" and tornado.state ~= "outro" then
                local distToPlayer = (rootPos - root.Position).Magnitude
                local funnelRadius = effectiveTopWidth * 0.4
                local innerRadius = effectiveTopWidth * 0.8
                local outerRadius = effectiveTopWidth * 2.5
                
                if distToPlayer <= outerRadius then
                    local strength_pull = 1 - (distToPlayer / outerRadius)
                    strength_pull = math.clamp(strength_pull, 0, 1)
                    
                    if humanoid.PlatformStand == false then
                        humanoid.PlatformStand = true
                    end
                    
                    if distToPlayer <= funnelRadius then
                        local upwardForce = Vector3.new(0, 50 * strength_pull + 20, 0)
                        root.AssemblyLinearVelocity = root.AssemblyLinearVelocity + upwardForce * dt * 20
                        
                        local toCenter = (rootPos - root.Position) * Vector3.new(1, 0, 1)
                        if toCenter.Magnitude > 0.5 then
                            local tangent = Vector3.new(-toCenter.Z, 0, toCenter.X).Unit
                            local rotationSpeed = 30 + (1 - strength_pull) * 20
                            root.AssemblyLinearVelocity = root.AssemblyLinearVelocity + tangent * rotationSpeed * dt * 5
                        end
                    elseif distToPlayer <= innerRadius then
                        local upwardForce = Vector3.new(0, 15 * strength_pull + 5, 0)
                        root.AssemblyLinearVelocity = root.AssemblyLinearVelocity + upwardForce * dt * 10
                        
                        local toCenter = (rootPos - root.Position) * Vector3.new(1, 0, 1)
                        if toCenter.Magnitude > 0.5 then
                            local tangent = Vector3.new(-toCenter.Z, 0, toCenter.X).Unit
                            local rotationSpeed = 15 + strength_pull * 15
                            root.AssemblyLinearVelocity = root.AssemblyLinearVelocity + tangent * rotationSpeed * dt * 3
                        end
                    end
                else
                    if humanoid.PlatformStand == true then
                        humanoid.PlatformStand = false
                    end
                end
            end

            -- Física de partes (mesmo código original)
            local params = OverlapParams.new()
            params.FilterType = Enum.RaycastFilterType.Exclude
            params.FilterDescendantsInstances = {tornado.Model, Player.Character}

            local partsInRadius = Workspace:GetPartBoundsInRadius(rootPos, searchRadius, params)

            for _, p in ipairs(partsInRadius) do
                if p:IsA("BasePart") then
                    local offset = p.Position - rootPos
                    local currentHeight = p.Position.Y - rootPos.Y
                    local horizontalDist = Vector3.new(offset.X, 0, offset.Z).Magnitude
                    local forceMult = strength * Settings.PullStrengthMult
                    local liftMult = Settings.LiftSpeed * (1 + probeCount * 0.15)
                    
                    local efLevel = tornado.EF_Level

                    if Settings.PhysicsMode == "Original" or Settings.PhysicsMode == "Better" then
                        if not p.Anchored then
                            local canLift = false
                            local mass = p:GetMass()
                            if efLevel <= 1 then
                                canLift = mass < 50
                            elseif efLevel <= 2 then
                                canLift = mass < 100
                            elseif efLevel <= 4 then
                                canLift = mass < 1000
                            else
                                canLift = true
                            end

                            if canLift then
                                local tangent = Vector3.new(-offset.Z, 0, offset.X).Unit
                                local inward = -Vector3.new(offset.X, 0, offset.Z).Unit
                                local upward = Vector3.new(0, 1.8 * liftMult, 0)
                                if Settings.PhysicsMode == "Better" then
                                    if currentHeight > totalHeight * 0.3 and math.random(1, 150) == 1 then
                                        local throwDir = Vector3.new(math.random(-100, 100), math.random(-20, 50), math.random(-100, 100)).Unit
                                        p.AssemblyLinearVelocity = throwDir * (forceMult * math.random(1.5, 3))
                                    else
                                        local distPercent = math.clamp(horizontalDist / searchRadius, 0, 1)
                                        local liftPower = 1 - distPercent
                                        if liftPower < 0.1 then liftPower = 0.1 end
                                        local suckForce = inward * (forceMult * (1.5 - distPercent))
                                        p.AssemblyLinearVelocity = (tangent * forceMult) + suckForce + (upward * forceMult)
                                    end
                                else
                                    if currentHeight > totalHeight * 0.95 then
                                        p.AssemblyLinearVelocity = Vector3.new(math.random(-100, 100), 50, math.random(-100, 100)).Unit * (forceMult * 1.5)
                                    else
                                        p.AssemblyLinearVelocity = (tangent * forceMult) + (inward * (forceMult * 0.4)) + (upward * forceMult)
                                    end
                                end
                            end
                        end

                    elseif Settings.PhysicsMode == "Realistic" then
                        if not p.Anchored then
                            InitializePartHealth(p)
                            
                            if not tornado.PendingSuck[p] then
                                local health = PartHealth[p] or 1
                                local maxHealth = PartHealthMax[p] or 1
                                local healthRatio = health / maxHealth
                                
                                local baseDelay = math.max(0.3, 5 - efLevel * 0.3)
                                local delayTime = baseDelay * (0.5 + (1 - healthRatio) * 0.5)
                                tornado.PendingSuck[p] = {firstSeen = now, delay = delayTime}
                            else
                                local data = tornado.PendingSuck[p]
                                if now - data.firstSeen >= data.delay then
                                    if CanLiftPartByEF(p, efLevel) then
                                        local distPercent = math.clamp(horizontalDist / searchRadius, 0, 1)
                                        local liftPower = 1 - distPercent
                                        if liftPower < 0.1 then liftPower = 0.1 end
                                        local tangent = Vector3.new(-offset.Z, 0, offset.X).Unit
                                        local inward = -Vector3.new(offset.X, 0, offset.Z).Unit
                                        local upward = Vector3.new(0, 3 * liftMult * liftPower * (1 + (1 - PartHealth[p]/PartHealthMax[p]) * 0.5), 0)
                                        local suckForce = inward * (forceMult * (1.5 - distPercent))
                                        p.AssemblyLinearVelocity = (tangent * forceMult) + suckForce + (upward * forceMult)
                                        
                                        if math.random(1, 50) == 1 then
                                            local damage = math.random(1, 2)
                                            if DamagePart(p, damage) then
                                                local explosion = Instance.new("Explosion", workspace)
                                                explosion.Position = p.Position
                                                explosion.BlastRadius = 2
                                                explosion.BlastPressure = 0
                                                explosion.ExplosionType = Enum.ExplosionType.NoCraters
                                                Debris:AddItem(explosion, 0.1)
                                            end
                                        end
                                    end
                                    tornado.PendingSuck[p] = nil
                                end
                            end
                        else
                            if not tornado.ProcessedAnchored[p] then
                                local size = p.Size.Magnitude
                                if IsSizeInEFRange(size, efLevel) then
                                    local unanchorDuration = math.random(3, 15)
                                    tornado.ProcessedAnchored[p] = {time = now, duration = unanchorDuration}
                                    p.Anchored = false
                                    local impulseDir = Vector3.new(math.random(-10,10), math.random(5,20), math.random(-10,10)).Unit
                                    p.AssemblyLinearVelocity = impulseDir * 10
                                end
                            else
                                local data = tornado.ProcessedAnchored[p]
                                if now - data.time >= data.duration then
                                    p.Anchored = true
                                    tornado.ProcessedAnchored[p] = nil
                                else
                                    if math.random(1, 30) == 1 then
                                        if DamagePart(p, 1) then
                                            p:Destroy()
                                            tornado.ProcessedAnchored[p] = nil
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end

            if root and humanoid and humanoid.SeatPart then
                local seat = humanoid.SeatPart
                local vehicleModel = seat.Parent
                if vehicleModel and vehicleModel:IsA("Model") then
                    local primaryPart = vehicleModel.PrimaryPart or seat
                    local vehicleDist = (primaryPart.Position - rootPos).Magnitude
                    if vehicleDist <= searchRadius * 1.5 then
                        primaryPart.AssemblyLinearVelocity = primaryPart.AssemblyLinearVelocity + Vector3.new(0, liftMult * baseStrength * 0.5, 0)
                    end
                end
            end
        end

        -- Atualiza o parágrafo de aviso do tornado
        if tornado.WarningParagraph then
            local currentWidth = tornado.targetFullTopWidth * tornado.currentScale
            local maxWidth = tornado.targetFullTopWidth
            local windspeed = tornado.MaxWindSpeed
            local dateStr = os.date("%d/%m/%Y %H:%M:%S", tornado.SpawnTime)
            
            local distanceStr = "N/A"
            if root then
                local dist = (tornado.Core.Position - root.Position).Magnitude
                distanceStr = string.format("%.1f studs", dist)
            end
            
            local dirAngle = math.deg(math.atan2(tornado.moveDirection.Z, tornado.moveDirection.X))
            local dirStr = string.format("%.1f°", dirAngle)
            
            local coordsStr = string.format("(%.1f, %.1f)", tornado.Core.Position.X, tornado.Core.Position.Z)
            
            local desc = string.format(
                "Dia: %s\nEF: %d\nWidth Atual: %.1f studs\nMax Width: %.1f studs\nWindspeed: %.1f\nDistância: %s\nDireção: %s\nCoordenadas: %s",
                dateStr, tornado.EF_Level, currentWidth, maxWidth, windspeed, distanceStr, dirStr, coordsStr
            )
            tornado.WarningParagraph:SetDesc(desc)
        end

        -- Notificação de perigo (mantém raio de aviso)
        if root and tornado.Active and tornado.state ~= "dead" then
            local effectiveTopWidth = tornado.targetFullTopWidth * tornado.currentScale
            local dangerRadius = effectiveTopWidth * 6
            local dist = (tornado.Core.Position - root.Position).Magnitude
            if dist <= dangerRadius then
                if not tornado.PlayerInDanger then
                    tornado.PlayerInDanger = true
                    WindUI:Notify({
                        Title = "⚠️ Perigo de Tornado!",
                        Content = string.format("Você entrou na zona de perigo de um tornado EF%d! Distância: %.0f studs.", tornado.EF_Level, dist),
                        Duration = 5,
                        Icon = "alert"
                    })
                end
            else
                tornado.PlayerInDanger = false
                if humanoid and humanoid.PlatformStand then
                    humanoid.PlatformStand = false
                end
            end
        end
    end

    UpdateFogEffect()

    -- HUD
    local maxDisplayDist = 200
    if nearestTornado then
        maxDisplayDist = math.max(200, nearestTornado.targetFullTopWidth * nearestTornado.currentScale * 3)
    end
    if Settings.ShowHUD and HudFrame then
        if nearestTornado and minDist <= maxDisplayDist then
            HudFrame.Visible = true
            local textLabels = HudFrame.Frame:GetChildren()
            local mesonetText, distText, efText, windText
            for _, child in ipairs(textLabels) do
                if child:IsA("TextLabel") then
                    if child.Text:find("Mesonet") then mesonetText = child
                    elseif child.Text:find("Distância") then distText = child
                    elseif child.Text:find("EF") then efText = child
                    elseif child.Text:find("Windspeed") then windText = child
                    end
                end
            end
            if nearestTornado then
                local probeCount = GetNearbyProbeCount(nearestTornado)
                if mesonetText then mesonetText.Text = "Mesonet: " .. probeCount end
                if distText then distText.Text = "Distância: " .. math.floor(minDist) end
                if efText then efText.Text = "EF: EF" .. nearestTornado.EF_Level end
                if windText then
                    local windSpeed = math.floor(nearestTornado.MaxWindSpeed * 10) / 10
                    windText.Text = "Windspeed: " .. windSpeed
                end
            end
        else
            HudFrame.Visible = false
        end
    else
        if HudFrame then HudFrame.Visible = false end
    end

    -- Shake da câmera
    if root and camera then
        local shakeMagnitude = 0
        for _, tornado in ipairs(ActiveTornados) do
            if tornado.Active and tornado.state ~= "dead" and tornado.state ~= "outro" then
                local dist = (root.Position - tornado.Core.Position).Magnitude
                local maxDist = tornado.targetFullTopWidth * tornado.currentScale * 3
                if dist < maxDist then
                    shakeMagnitude = math.max(shakeMagnitude, 1 - (dist / maxDist))
                end
            end
        end

        if shakeMagnitude > 0 then
            local shakeIntensity = shakeMagnitude * 1.0
            local maxRoll = 60
            local maxPitch = 30
            local maxYaw = 15
            local roll = math.rad(math.random(-maxRoll, maxRoll)) * shakeIntensity
            local pitch = math.rad(math.random(-maxPitch, maxPitch)) * shakeIntensity
            local yaw = math.rad(math.random(-maxYaw, maxYaw)) * shakeIntensity
            local yOffset = math.random(-7, 7) * shakeIntensity
            local xOffset = math.random(-3, 3) * shakeIntensity
            local zOffset = math.random(-3, 3) * shakeIntensity
            local shakeCF = CFrame.Angles(roll, yaw, pitch) * CFrame.new(xOffset, yOffset, zOffset)
            camera.CFrame = camera.CFrame * shakeCF
        end
    end
end)

WindUI:Notify({
    Title = "Tornado-Geek Carregado!",
    Content = "V1.0 – EF0-EF6 • Stun ao ser puxado • Hitbox aumentada • Special multi • Auto spawn 160-230s",
    Duration = 9,
    Icon = "orbit",
})

WindUI:SetTheme("Default")

Features & Information

(AI slop, srry) 🇺🇸: Basically an Imagination free tornado script (it's in Portuguese) If some stuff is inaccurate, Tell me in the comments, Ill fix next upd. (Credits to VoidX5 for template "TornadoV6") (Blank thumbnail for Now)
#TornadoGeek#TG#TornadoGUI

Community Comments (0)

No comments yet for this script.

More Scripts for Universal Scripts