
KeylessUniversal

-- 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")No comments yet for this script.



