-- 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")