---------------------------------------------------- -- SERVICES ---------------------------------------------------- local Players = game:GetService("Players") local RunService = game:GetService("RunService") local Lighting = game:GetService("Lighting") local Player = Players.LocalPlayer local camera = workspace.CurrentCamera ---------------------------------------------------- -- SETTINGS ---------------------------------------------------- local MAP_RADIUS = 10000 local PROXIMITY_SOUND_ID = "rbxassetid://119423241285421" local HAIL_SOUND_ID = "rbxassetid://133499134409591" local CLOSE_WIND_SOUND_ID = "rbxassetid://139095330035399" local THUNDER_SOUND_ID = "rbxassetid://139319051979882" local PROBE_DEPLOY_SOUND_ID = "rbxassetid://107860062629614" local supercellId = 17866293114 local tornadoMeshId = 6002059830 local FUNNEL_TIME = 49 local WIND_RADIUS = { [0] = 600, [1] = 900, [2] = 2300, [3] = 6089, [4] = 6100, [5] = 6382 } local function getOuterRadius() return WIND_RADIUS[EF_LEVEL] or 2800 end local PROBE_EF_TABLE = { [0] = { radius = 99, wind = 80 }, [1] = { radius = 100, wind = 100 }, [2] = { radius = 100, wind = 130 }, [3] = { radius = 2089, wind = 155 }, [4] = { radius = 3300, wind = 185 }, [5] = { radius = 4982, wind = 250 }, } ---------------------------------------------------- -- πŸŒ₯️ STORM DARKNESS ---------------------------------------------------- Lighting.GlobalShadows = true Lighting.Brightness = 1 Lighting.ClockTime = 15 Lighting.Ambient = Color3.fromRGB(40,40,45) Lighting.OutdoorAmbient = Color3.fromRGB(55,55,60) Lighting.FogColor = Color3.fromRGB(45,45,50) Lighting.FogStart = 0 Lighting.FogEnd = 5000 ---------------------------------------------------- -- CUSTOM SKYBOX ---------------------------------------------------- local oldSky = Lighting:FindFirstChildOfClass("Sky") if oldSky then oldSky:Destroy() end local Sky = Instance.new("Sky") Sky.Name = "StormSky" Sky.SkyboxBk = "rbxassetid://1310975167" Sky.SkyboxDn = "rbxassetid://1310975167" Sky.SkyboxFt = "rbxassetid://1310975167" Sky.SkyboxLf = "rbxassetid://1310975167" Sky.SkyboxRt = "rbxassetid://1310975167" Sky.SkyboxUp = "rbxassetid://1310975167" Sky.SunTextureId = "" Sky.MoonTextureId = "" Sky.Parent = Lighting ---------------------------------------------------- -- COLOR CORRECTION ---------------------------------------------------- local oldCC = Lighting:FindFirstChild("StormColorCorrection") if oldCC then oldCC:Destroy() end local cc = Instance.new("ColorCorrectionEffect") cc.Name = "StormColorCorrection" cc.Parent = Lighting cc.Brightness = -0.08 cc.Contrast = 0.25 cc.Saturation = -0.3 cc.TintColor = Color3.fromRGB(170,180,170) ---------------------------------------------------- -- 🌫️ ATMOSPHERE SYSTEM (EF-BASED STORM FOG) ---------------------------------------------------- local EF_ATMOSPHERE = { [0] = { density = 0.8, haze = 2.0 }, [1] = { density = 0.16, haze = 3.2 }, [2] = { density = 0.24, haze = 4.4 }, [3] = { density = 0.75, haze = 5.6 }, [4] = { density = 0.88, haze = 6.8 }, [5] = { density = 0.90, haze = 8.5 }, } ---------------------------------------------------- -- CREATE ATMOSPHERE ---------------------------------------------------- local atmosphere = Lighting:FindFirstChildOfClass("Atmosphere") if atmosphere then atmosphere:Destroy() end atmosphere = Instance.new("Atmosphere") atmosphere.Parent = Lighting atmosphere.Density = 0.35 atmosphere.Offset = 0.3 atmosphere.Color = Color3.fromRGB(120, 120, 120) atmosphere.Decay = Color3.fromRGB(60, 60, 60) atmosphere.Glare = 0 atmosphere.Haze = 4 ---------------------------------------------------- -- UPDATE FUNCTION (EF CONTROL) ---------------------------------------------------- local function updateAtmosphere(fade) if not atmosphere then return end local cfg = EF_ATMOSPHERE[EF_LEVEL] or EF_ATMOSPHERE[3] fade = fade or 1 atmosphere.Density = cfg.density * fade atmosphere.Haze = cfg.haze * fade -- EF-based storm tint (darker = stronger storm) local r = math.clamp(120 - EF_LEVEL * 7, 20, 120) local g = math.clamp(120 - EF_LEVEL * 6, 20, 120) local b = math.clamp(120 - EF_LEVEL * 7, 20, 120) atmosphere.Color = Color3.fromRGB(r, g, b) -- deeper decay = heavier storm atmosphere local dr = math.clamp(60 - EF_LEVEL * 5, 10, 60) local dg = math.clamp(60 - EF_LEVEL * 5, 10, 60) local db = math.clamp(60 - EF_LEVEL * 5, 10, 60) atmosphere.Decay = Color3.fromRGB(dr, dg, db) atmosphere.Glare = 0 end ---------------------------------------------------- -- ENABLE / DISABLE HOOKS ---------------------------------------------------- local function enableAtmosphere() if atmosphere then atmosphere.Parent = Lighting end updateAtmosphere(1) end local function disableAtmosphere() if atmosphere then atmosphere.Parent = nil end end ---------------------------------------------------- -- DECAY SUPPORT (CALL DURING DISSIPATION) ---------------------------------------------------- local function updateAtmosphereDecay(fade) updateAtmosphere(fade) end ---------------------------------------------------- -- STAGES ---------------------------------------------------- local STAGE = { WEDGE = 1, FUNNEL = 2, TORNADO = 3 } local currentStage = STAGE.WEDGE ---------------------------------------------------- -- EF SYSTEM ---------------------------------------------------- local EF_LEVEL = math.random(0,5) local EF_SETTINGS = { [0] = { sizeXZ = 20, sizeY = 120, wind = 80 }, -- 65-85 mph [1] = { sizeXZ = 40, sizeY = 200, wind = 100 }, -- 86-110 mph [2] = { sizeXZ = 70, sizeY = 350, wind = 130 }, -- 111-135 mph [3] = { sizeXZ = 250, sizeY = 420, wind = 155 }, -- 136-165 mph [4] = { sizeXZ = 338, sizeY = 500, wind = 185 }, -- 166-200 mph [5] = { sizeXZ = 468, sizeY = 650, wind = 250 }, -- 200+ mph } local ef = EF_SETTINGS[EF_LEVEL] ---------------------------------------------------- -- SPEED ---------------------------------------------------- local EF_SPEED = { [0] = 40, [1] = 60, [2] = 90, [3] = 70, [4] = 100, [5] = 150, } local currentSpeed = EF_SPEED[EF_LEVEL] or 80 ---------------------------------------------------- -- ROTATION ---------------------------------------------------- local HELICITY = math.random(80, 400) local rotationStrength = (ef.wind / 300) + (HELICITY / 500) local angularVelocity = 0 ---------------------------------------------------- -- STATE ---------------------------------------------------- local funnelDone = false local tornadoFullyFormed = false local stormEnabled = true local stormDestroyed = false ---------------------------------------------------- -- SPAWN AT CENTER ---------------------------------------------------- local spawnOrigin = Vector3.new(0,65,0) ---------------------------------------------------- -- CORE TORNADO ---------------------------------------------------- local tornado = Instance.new("Part") tornado.Anchored = true tornado.CanCollide = false tornado.Transparency = 1 tornado.Position = spawnOrigin tornado.Parent = workspace local mesh = Instance.new("SpecialMesh") mesh.MeshId = "rbxassetid://" .. tornadoMeshId mesh.Scale = Vector3.new(0.5,40,0.5) mesh.Parent = tornado ---------------------------------------------------- -- 🌫️ DISTANT WHIRLING SMOKE FIELD ---------------------------------------------------- local smokePart = Instance.new("Part") smokePart.Anchored = true smokePart.CanCollide = false smokePart.Transparency = 1 smokePart.Size = Vector3.new(1,1,1) smokePart.Parent = workspace local att = Instance.new("Attachment") att.Parent = smokePart local smoke = Instance.new("ParticleEmitter") smoke.Parent = att smoke.Texture = "rbxassetid://99854528239110" ---------------------------------------------------- -- πŸŒͺ️ WIDE AREA EMISSION (FAR AWAY CLOUD FIELD) ---------------------------------------------------- smoke.Rate = 1200 smoke.Lifetime = NumberRange.new(6, 12) -- BIG spread = distant cloud sheet smoke.SpreadAngle = Vector2.new(180, 180) -- slower base motion so wind can shape it smoke.Speed = NumberRange.new(10, 35) smoke.Drag = 3 smoke.VelocityInheritance = 0.15 smoke.Rotation = NumberRange.new(0, 360) smoke.RotSpeed = NumberRange.new(-120, 120) smoke.Size = NumberSequence.new({ NumberSequenceKeypoint.new(0, 8), NumberSequenceKeypoint.new(1, 2) }) smoke.Transparency = NumberSequence.new({ NumberSequenceKeypoint.new(0, 0.35), NumberSequenceKeypoint.new(1, 1) }) smoke.Color = ColorSequence.new( Color3.fromRGB(90, 90, 90), Color3.fromRGB(20, 20, 20) ) smoke.LightInfluence = 0 smoke.LightEmission = 0 ---------------------------------------------------- -- πŸŒͺ️ GLOBAL WHIRL + SUCTION SYSTEM ---------------------------------------------------- RunService.Heartbeat:Connect(function() if not tornado or not tornado.Parent then return end local tornadoPos = tornado.Position -- move smoke field around tornado (keeps it "far and wide") smokePart.Position = tornadoPos + Vector3.new( math.random(-2500, 2500), 200, math.random(-2500, 2500) ) local offset = tornadoPos - smokePart.Position local dist = offset.Magnitude if dist < 1 then return end local dir = offset.Unit ---------------------------------------------------- -- πŸŒͺ️ SUCTION (pull toward center) ---------------------------------------------------- local suctionStrength = math.clamp(1 - (dist / 4000), 0, 1) smoke.Acceleration = dir * (180 * suctionStrength) ---------------------------------------------------- -- πŸŒͺ️ WHIRL / SPIN (KEY EFFECT) ---------------------------------------------------- local tangent = Vector3.new(-dir.Z, 0, dir.X) local swirlStrength = (0.6 + (1 - suctionStrength)) smoke.Acceleration += tangent * (140 * swirlStrength) ---------------------------------------------------- -- πŸ”₯ tighter funnel near center ---------------------------------------------------- smoke.Speed = NumberRange.new( 30 + (100 * suctionStrength), 80 + (200 * suctionStrength) ) end) ---------------------------------------------------- -- SUPERCELL ---------------------------------------------------- local supercell = game:GetObjects("rbxassetid://" .. supercellId)[1] supercell.Parent = workspace supercell.PrimaryPart = supercell:FindFirstChildWhichIsA("BasePart") local cellOffset = Vector3.new(0,400,0) ---------------------------------------------------- -- WALL CLOUD ---------------------------------------------------- local wallCloud = Instance.new("Part") wallCloud.Anchored = true wallCloud.CanCollide = false wallCloud.Transparency = 1 wallCloud.Size = Vector3.new(1,1,1) wallCloud.Parent = workspace local wallAttachment = Instance.new("Attachment", wallCloud) local wallEmitter = Instance.new("ParticleEmitter") wallEmitter.Parent = wallAttachment wallEmitter.Texture = "rbxassetid://7712212248" wallEmitter.Rate = 0 wallEmitter.Lifetime = NumberRange.new(6,12) wallEmitter.Speed = NumberRange.new(4,18) wallEmitter.Rotation = NumberRange.new(0,360) wallEmitter.RotSpeed = NumberRange.new(-25,25) wallEmitter.SpreadAngle = Vector2.new(180,180) wallEmitter.Acceleration = Vector3.new(0,-1,0) wallEmitter.Size = NumberSequence.new({ NumberSequenceKeypoint.new(0,80), NumberSequenceKeypoint.new(0.5,140), NumberSequenceKeypoint.new(1,220) }) wallEmitter.Transparency = NumberSequence.new({ NumberSequenceKeypoint.new(0,0.55), NumberSequenceKeypoint.new(0.5,0.72), NumberSequenceKeypoint.new(1,1) }) wallEmitter.Color = ColorSequence.new({ ColorSequenceKeypoint.new(0,Color3.fromRGB(45,45,45)), ColorSequenceKeypoint.new(0.5,Color3.fromRGB(70,70,70)), ColorSequenceKeypoint.new(1,Color3.fromRGB(20,20,20)) }) wallEmitter.LightInfluence = 0 wallEmitter.LightEmission = 0 local wallSpin = 0 ---------------------------------------------------- -- SOUND ---------------------------------------------------- local tornadoSound = Instance.new("Sound", workspace) tornadoSound.SoundId = PROXIMITY_SOUND_ID tornadoSound.Looped = true tornadoSound.Volume = 0 local closeWindSound = Instance.new("Sound", workspace) closeWindSound.SoundId = CLOSE_WIND_SOUND_ID closeWindSound.Looped = true local hailSound = Instance.new("Sound", workspace) hailSound.SoundId = HAIL_SOUND_ID hailSound.Looped = true local thunderSound = Instance.new("Sound", workspace) thunderSound.SoundId = THUNDER_SOUND_ID ---------------------------------------------------- -- PROBE DEPLOY SOUND ---------------------------------------------------- local deploySound = Instance.new("Sound") deploySound.SoundId = "rbxassetid://107860062629614" deploySound.Volume = 1 deploySound.Parent = workspace ---------------------------------------------------- -- πŸŒͺ️ GLOBAL WIND SOUND SYSTEM (3000 STUDS TOGGLE) ---------------------------------------------------- local globalWindSound = nil local function createGlobalWind() if globalWindSound then return end globalWindSound = Instance.new("Sound") globalWindSound.Name = "GlobalWind" globalWindSound.SoundId = PROXIMITY_SOUND_ID globalWindSound.Looped = true local EF_GLOBAL_VOLUME = { [0] = 0.15, -- weak distant breeze [1] = 0.25, [2] = 0.40, [3] = 0.60, [4] = 0.85, [5] = 1.00 -- maximum roar } globalWindSound.Volume = EF_GLOBAL_VOLUME[EF_LEVEL] or 0.4 globalWindSound.RollOffMode = Enum.RollOffMode.InverseTapered local EF_GLOBAL_DISTANCE = { [0] = 700, [1] = 995, [2] = 1700, [3] = 2999, [4] = 6500, [5] = 6882 } globalWindSound.RollOffMode = Enum.RollOffMode.InverseTapered globalWindSound.RollOffMaxDistance = EF_GLOBAL_DISTANCE[EF_LEVEL] or 5500 globalWindSound.EmitterSize = 50 globalWindSound.Parent = workspace globalWindSound:Play() end local function removeGlobalWind() if globalWindSound then globalWindSound:Stop() globalWindSound:Destroy() globalWindSound = nil end end -- initial state (if player already inside storm range) task.defer(function() if stormEnabled then createGlobalWind() end end) ---------------------------------------------------- -- πŸŒͺ️ 10000 studs STUDS DISTANCE HOOK ---------------------------------------------------- RunService.RenderStepped:Connect(function() local char = Player.Character if not char then return end local hrp = char:FindFirstChild("HumanoidRootPart") if not hrp then return end if not tornado or not tornado.Parent then if globalWindSound then globalWindSound:Stop() globalWindSound:Destroy() globalWindSound = nil end return end if not tornadoFullyFormed and globalWindSound then removeGlobalWind() end local dist = (hrp.Position - tornado.Position).Magnitude ------------------------------------------------ -- OUTSIDE STORM ZONE ------------------------------------------------ if dist > 10000 then stormEnabled = false if globalWindSound then globalWindSound:Stop() globalWindSound:Destroy() globalWindSound = nil end if atmosphere then atmosphere.Parent = nil end if cc then cc.Parent = nil end Lighting.Ambient = Color3.fromRGB(128,128,128) Lighting.OutdoorAmbient = Color3.fromRGB(128,128,128) Lighting.FogEnd = 100000 Lighting.Brightness = 2 ------------------------------------------------ -- INSIDE STORM ZONE ------------------------------------------------ else stormEnabled = true if not globalWindSound then createGlobalWind() end if atmosphere then atmosphere.Parent = Lighting end if cc then cc.Parent = Lighting end Lighting.Ambient = Color3.fromRGB(40,40,45) Lighting.OutdoorAmbient = Color3.fromRGB(55,55,60) Lighting.FogEnd = 5000 Lighting.Brightness = 1 end end) if tornadoFullyFormed then if not globalWindSound then createGlobalWind() end end ---------------------------------------------------- -- LIGHTNING FLASHES (WHITE WORLD ILLUMINATION) ---------------------------------------------------- local function lightningFlash() local oldBrightness = Lighting.Brightness local oldAmbient = Lighting.Ambient local oldOutdoor = Lighting.OutdoorAmbient local oldFog = Lighting.FogEnd -- πŸ’₯ instant white flash (overexposed world) Lighting.Brightness = 10 Lighting.Ambient = Color3.fromRGB(255, 255, 255) Lighting.OutdoorAmbient = Color3.fromRGB(255, 255, 255) Lighting.FogEnd = 100000 -- optional extra punch (if you want more realism) local ccFlash = Instance.new("ColorCorrectionEffect") ccFlash.Brightness = 0.4 ccFlash.Contrast = 0.2 ccFlash.Saturation = -0.2 ccFlash.TintColor = Color3.fromRGB(255, 255, 255) ccFlash.Parent = Lighting -- thunder sound sync thunderSound:Play() -- flash duration task.wait(0.12) -- fade back quickly Lighting.Brightness = oldBrightness Lighting.Ambient = oldAmbient Lighting.OutdoorAmbient = oldOutdoor Lighting.FogEnd = oldFog -- remove flash effect game:GetService("Debris"):AddItem(ccFlash, 0.2) end task.spawn(function() while true do task.wait(math.random(8, 20)) if stormEnabled and math.random(1, 2) == 1 then lightningFlash() end end end) ---------------------------------------------------- -- WIND FIELD ---------------------------------------------------- local function safeUnit(v) if v.Magnitude < 0.05 then return Vector3.new(1,0,0) end return v.Unit end local function getOuterRadius() return WIND_RADIUS[EF_LEVEL] or 2800 end local function pressureProfile(dist) local x = math.clamp(dist / getOuterRadius(), 0, 1) return (1 - x)^2.6 end local function windField(pos) local offset = tornado.Position - pos local dist = offset.Magnitude local dir = safeUnit(offset) local pressure = pressureProfile(dist) local swirlPeak = math.exp(-((dist - 600)^2)/(2*700^2)) local tangent = Vector3.new(-dir.Z,0,dir.X) local inflow = dir * (pressure * ef.wind) local swirl = tangent * (rotationStrength * ef.wind * swirlPeak) local lift = Vector3.new( 0, math.clamp(140 - dist*0.06,0,140), 0 ) return inflow + swirl + lift end ---------------------------------------------------- -- πŸŒͺ️ LOW-LAG INTERIOR MODEL SYSTEM ---------------------------------------------------- local INTERIOR_MODEL_ID = "rbxassetid://8612582067" local interiorModels = {} local MAX_MODELS = 300 -- Please reduce models if you have a low end device local template = game:GetObjects(INTERIOR_MODEL_ID)[1] ---------------------------------------------------- -- CREATE MODELS ---------------------------------------------------- for i = 1, MAX_MODELS do local m = template:Clone() m.Parent = workspace if not m.PrimaryPart then m.PrimaryPart = m:FindFirstChildWhichIsA("BasePart") end table.insert(interiorModels,{ model = m, angle = math.random() * math.pi * 2, radius = math.random(), height = math.random(), speed = 200 / 100 }) end ---------------------------------------------------- -- MOTION SYSTEM (GROUND-SNAPPED) ---------------------------------------------------- local function getGroundY(position, ignoreList) local origin = position + Vector3.new(0, 1000, 0) local direction = Vector3.new(0, -5000, 0) local params = RaycastParams.new() params.FilterType = Enum.RaycastFilterType.Blacklist params.FilterDescendantsInstances = ignoreList or {} local result = workspace:Raycast(origin, direction, params) if result then return result.Position.Y end return position.Y end RunService.Heartbeat:Connect(function(dt) if not tornado or not tornado.Parent then return end local probe = PROBE_EF_TABLE[EF_LEVEL] if not probe then return end local maxRadius = probe.radius for _, data in ipairs(interiorModels) do local m = data.model if m and m.PrimaryPart then -- spin update data.angle += dt * data.speed * 8 -- horizontal orbit position around tornado local baseX = math.cos(data.angle) * (data.radius * maxRadius) local baseZ = math.sin(data.angle) * (data.radius * maxRadius) local worldPos = tornado.Position + Vector3.new(baseX, 0, baseZ) -- 🌍 ground snapping via raycast local groundY = getGroundY(worldPos, {m}) local pos = Vector3.new( worldPos.X, groundY, worldPos.Z ) -- optional: slight lift so it doesn't clip terrain pos += Vector3.new(0, 2, 0) -- apply movement + rotation m:PivotTo( CFrame.new(pos) * CFrame.Angles(0, data.angle * 3, 0) ) end end end) ---------------------------------------------------- -- 🏠 INDOOR OCCLUSION SYSTEM -- Detects roof + walls only ---------------------------------------------------- local function getOcclusionFactor(hrp) local origin = hrp.Position local params = RaycastParams.new() params.FilterType = Enum.RaycastFilterType.Blacklist params.FilterDescendantsInstances = {Player.Character} ------------------------------------------------ -- ROOF CHECK ------------------------------------------------ local roofHit = workspace:Raycast( origin, Vector3.new(0, 500, 0), params ) ------------------------------------------------ -- WALL CHECKS ------------------------------------------------ local wallHits = 0 local wallDirections = { Vector3.new(250, 0, 0), Vector3.new(-250, 0, 0), Vector3.new(0, 0, 250), Vector3.new(0, 0, -250), } for _, dir in ipairs(wallDirections) do local result = workspace:Raycast( origin, dir, params ) if result then wallHits += 1 end end ------------------------------------------------ -- CLASSIFICATION ------------------------------------------------ -- Fully indoors if roofHit and wallHits >= 2 then return 1 end -- Partially sheltered if roofHit and wallHits >= 1 then return 0.5 end -- Outdoors return 0 end ---------------------------------------------------- -- πŸŒͺ️ PLAYER WIND + SFX (WITH OCCLUSION) ---------------------------------------------------- RunService.RenderStepped:Connect(function() local char = Player.Character if not char then return end local hrp = char:FindFirstChild("HumanoidRootPart") if not hrp then return end local dist = (hrp.Position - tornado.Position).Magnitude local occlusion = getOcclusionFactor(hrp) local occludedVolumeMultiplier = 1 - (occlusion * 0.75) ---------------------------------------------------- -- DARKNESS / ATMOSPHERE ZONE LOGIC ---------------------------------------------------- if dist > 10000 and stormEnabled then stormEnabled = false if atmosphere then atmosphere.Parent = nil end if cc then cc.Parent = nil end Lighting.Ambient = Color3.fromRGB(128,128,128) Lighting.OutdoorAmbient = Color3.fromRGB(128,128,128) Lighting.FogEnd = 100000 Lighting.Brightness = 2 elseif dist <= 10000 and not stormEnabled then stormEnabled = true if atmosphere then atmosphere.Parent = Lighting end if cc then cc.Parent = Lighting end Lighting.Ambient = Color3.fromRGB(40,40,45) Lighting.OutdoorAmbient = Color3.fromRGB(55,55,60) Lighting.FogEnd = 5000 Lighting.Brightness = 1 end ---------------------------------------------------- -- ❄️ PRE-TORNADO SAFETY PUSH ---------------------------------------------------- if not tornadoFullyFormed then if EF_LEVEL ~= 0 and dist < 2500 then hrp.AssemblyLinearVelocity += safeUnit(tornado.Position - hrp.Position) * 2 end return end ---------------------------------------------------- -- πŸŒͺ️ EF DAMAGE SYSTEM (SHREDDING VERSION) ---------------------------------------------------- local function getRandomAxis() return Vector3.new( math.random(-100,100), math.random(-100,100), math.random(-100,100) ) end local function shredPart(part, intensity) if not part or not part.Parent then return end if not part:IsA("BasePart") then return end -- prevent double shredding if part:GetAttribute("Shredded") then return end part:SetAttribute("Shredded", true) ------------------------------------------------ -- STEP 1: weaken structure instead of instant break ------------------------------------------------ for _, c in ipairs(part:GetChildren()) do if c:IsA("Weld") or c:IsA("WeldConstraint") then c:Destroy() end end ------------------------------------------------ -- STEP 2: apply physical tearing force ------------------------------------------------ part.Anchored = false local bodyForce = Instance.new("BodyVelocity") bodyForce.Velocity = (Vector3.new( math.random(-40,40), math.random(30,80), math.random(-40,40) )) * (intensity * 2) bodyForce.MaxForce = Vector3.new(1e6, 1e6, 1e6) bodyForce.P = 5000 bodyForce.Parent = part game.Debris:AddItem(bodyForce, 0.15) ------------------------------------------------ -- STEP 3: rotational shear (key for tornado feel) ------------------------------------------------ local spin = Instance.new("BodyAngularVelocity") spin.AngularVelocity = getRandomAxis() * intensity spin.MaxTorque = Vector3.new(1e7, 1e7, 1e7) spin.P = 5000 spin.Parent = part game.Debris:AddItem(spin, 0.2) ------------------------------------------------ -- STEP 4: EF-based fragmentation ------------------------------------------------ if EF_LEVEL >= 3 then task.delay(0.1, function() if part and part.Parent then -- micro-shatter effect part.Size *= Vector3.new(0.9, 0.9, 0.9) end end) end if EF_LEVEL >= 4 then task.delay(0.2, function() if part and part.Parent then part:BreakJoints() -- delayed, not instant end end) end if EF_LEVEL >= 5 then task.delay(0.25, function() if part and part.Parent then part:Destroy() -- full annihilation only at EF5 end end) end end ---------------------------------------------------- -- πŸŒͺ️ WIND FIELD ---------------------------------------------------- local wind = Vector3.new(0,0,0) if EF_LEVEL ~= 0 then wind = windField(hrp.Position) end local probe = PROBE_EF_TABLE[EF_LEVEL] if probe then local distFactor = math.clamp(1 - dist / probe.radius, 0, 1) local windBoost = probe.wind * distFactor end local outer = getOuterRadius() local edgeBoost = math.clamp(1 - dist / outer, 0, 1)^3 -- detect seating local humanoid = char:FindFirstChildOfClass("Humanoid") local isSeated = humanoid and humanoid.SeatPart ~= nil -- πŸŒͺ️ EF WIND SYSTEM (EF1 = VEHICLE/SEAT ONLY) local humanoid = char:FindFirstChildOfClass("Humanoid") local seatPart = humanoid and humanoid.SeatPart if EF_LEVEL ~= 0 then -- EF1: ONLY affect seats / vehicles if EF_LEVEL == 1 then return end -- EF2+ normal wind affects players hrp.AssemblyLinearVelocity += wind * (0.08 + edgeBoost * 0.25) end smoke.Rate = 200 + (800 * edgeBoost) ---------------------------------------------------- -- πŸ”Š WIND SOUNDS (OCCLUDED) ---------------------------------------------------- if dist < 7000 then if not tornadoSound.IsPlaying then tornadoSound:Play() end tornadoSound.Volume = (0.8 + (1 - dist/800)) * occludedVolumeMultiplier else tornadoSound:Stop() end if not tornado or not tornado.Parent or stormDestroyed then if globalWindSound and globalWindSound.Parent then task.spawn(function() local startVol = globalWindSound.Volume for i = 1, 30 do task.wait(0.05) if globalWindSound then globalWindSound.Volume = startVol * (1 - i / 30) end end if globalWindSound then globalWindSound:Stop() globalWindSound:Destroy() globalWindSound = nil end end) end return end if dist < 500 then if not closeWindSound.IsPlaying then closeWindSound:Play() end closeWindSound.Volume = (1 + (1 - dist/500)) * occludedVolumeMultiplier else closeWindSound:Stop() end end) ---------------------------------------------------- -- 🌨️ HAIL SYSTEM (WITH OCCLUSION) ---------------------------------------------------- RunService.RenderStepped:Connect(function() local char = Player.Character if not char then return end local hrp = char:FindFirstChild("HumanoidRootPart") if not hrp then return end local dist = (hrp.Position - tornado.Position).Magnitude local occlusion = getOcclusionFactor(hrp) local occludedVolumeMultiplier = 1 - (occlusion * 0.75) if dist < 42000 then if not hailSound.IsPlaying then hailSound:Play() end hailSound.Volume = 0.6 * occludedVolumeMultiplier else hailSound:Stop() end end) ---------------------------------------------------- -- TOOL SETUP ---------------------------------------------------- local tool = Instance.new("Tool") tool.Name = "Tornado probe" tool.RequiresHandle = false tool.CanBeDropped = false tool.Parent = Player:WaitForChild("Backpack") local mouse local spawnedModels = {} tool.Equipped:Connect(function() mouse = Player:GetMouse() end) ---------------------------------------------------- -- MODEL SPAWN FUNCTION (FIXED) ---------------------------------------------------- local function spawnStormModel(position) local model = game:GetObjects("rbxassetid://4857018987")[1] if not model then return end model.Parent = workspace if not model.PrimaryPart then model.PrimaryPart = model:FindFirstChildWhichIsA("BasePart") end if not model.PrimaryPart then return model end ---------------------------------------------------- -- πŸ“ SNAP TO GROUND ---------------------------------------------------- local rayOrigin = position + Vector3.new(0, 500, 0) local rayDir = Vector3.new(0, -2000, 0) local params = RaycastParams.new() params.FilterType = Enum.RaycastFilterType.Blacklist params.FilterDescendantsInstances = {model} local result = workspace:Raycast(rayOrigin, rayDir, params) local finalPos = position if result then finalPos = result.Position end ---------------------------------------------------- -- πŸ”„ 90Β° ROTATION (Y AXIS) ---------------------------------------------------- local cf = CFrame.new(finalPos) * CFrame.Angles(0, math.rad(90), 0) model:PivotTo(cf) ---------------------------------------------------- -- πŸͺ§ BILLBOARD UI ---------------------------------------------------- local billboard = Instance.new("BillboardGui") billboard.Size = UDim2.new(0, 250, 0, 80) billboard.StudsOffset = Vector3.new(0, 6, 0) billboard.AlwaysOnTop = true billboard.Parent = model.PrimaryPart local text = Instance.new("TextLabel") text.Size = UDim2.new(1, 0, 1, 0) text.BackgroundTransparency = 1 text.TextScaled = true text.Font = Enum.Font.GothamBold text.TextColor3 = Color3.fromRGB(255, 255, 255) text.TextStrokeTransparency = 0.3 text.Parent = billboard ---------------------------------------------------- -- πŸ“‘ LIVE UPDATE ---------------------------------------------------- task.spawn(function() while model and model.Parent do task.wait(0.1) local tornadoPos = tornado and tornado.Parent and tornado.Position local dist = tornadoPos and (model.PrimaryPart.Position - tornadoPos).Magnitude or math.huge local ef = EF_LEVEL or 0 local wind = (EF_SETTINGS[ef] and EF_SETTINGS[ef].wind) or 0 local displayEF = "EF " .. tostring(ef) local displayWind = wind ------------------------------------------------ -- πŸŒͺ️ CLOSE RANGE BOOST (CLAMPED) ------------------------------------------------ local probe = PROBE_EF_TABLE[EF_LEVEL] local displayEF = "EF-U" local displayWind = "???" if probe and dist <= probe.radius then displayEF = "EF-" .. tostring(EF_LEVEL) displayWind = tostring(probe.wind) end text.Text = displayEF .. " (WINDSPEED: " .. displayWind .. ")" ------------------------------------------------ -- ❓ OUT OF RANGE SIGNAL LOSS ------------------------------------------------ if dist > 6000 then displayEF = "EF-U" displayWind = "???" end text.Text = displayEF .. " (WINDSPEED: " .. tostring(displayWind) .. ")" end end) return model end ---------------------------------------------------- -- πŸ” TOGGLE SPAWN / REMOVE ---------------------------------------------------- tool.Activated:Connect(function() if not mouse then return end local char = Player.Character if not char then return end local hrp = char:FindFirstChild("HumanoidRootPart") if not hrp then return end -- if exists β†’ undeploy (no sound) if #spawnedModels > 0 then for _, m in ipairs(spawnedModels) do if m and m.Parent then m:Destroy() end end table.clear(spawnedModels) return end tool.Activated:Connect(function() if not mouse then return end local char = Player.Character if not char then return end local hrp = char:FindFirstChild("HumanoidRootPart") if not hrp then return end -- Toggle off existing probe if #spawnedModels > 0 then for _, m in ipairs(spawnedModels) do if m and m.Parent then m:Destroy() end end table.clear(spawnedModels) return end -- Play deploy sound deploySound:Play() -- Spawn new probe local spawnPos = hrp.Position + (hrp.CFrame.LookVector * 12) local model = spawnStormModel(spawnPos) if model then table.insert(spawnedModels, model) end end) -- spawn new local spawnPos = hrp.Position + (hrp.CFrame.LookVector * 12) local model = spawnStormModel(spawnPos) if model then table.insert(spawnedModels, model) end end) ---------------------------------------------------- -- πŸŒͺ️ LIFETIME + POST DEATH FADE SYSTEM ---------------------------------------------------- local EF_LIFETIME_MINUTES = { [0] = 5, [1] = 10, [2] = 15, [3] = 20, [4] = 25, [5] = 35, } local lifetimeSeconds = (EF_LIFETIME_MINUTES[EF_LEVEL] or 2) * 60 local decayDuration = 45 local tornadoSpawnTime = 0 local decayStarted = false local stormDead = false local postDeathFade = false local postFadeStart = 0 local postFadeDuration = 20 task.spawn(function() ------------------------------------------------ -- WAIT UNTIL FULL SIZE ------------------------------------------------ repeat task.wait() until tornadoFullyFormed and mesh.Scale.Y >= ef.sizeY * 0.98 task.wait(3) tornadoSpawnTime = tick() print("πŸŒͺ️ Lifetime ACTIVE") ------------------------------------------------ -- MAIN LOOP ------------------------------------------------ while tornado and tornado.Parent and not stormDead do local aliveTime = tick() - tornadoSpawnTime ------------------------------------------------ -- START DECAY ------------------------------------------------ if aliveTime >= lifetimeSeconds then decayStarted = true end ------------------------------------------------ -- DECAY SYSTEM ------------------------------------------------ if decayStarted then local decayTime = aliveTime - lifetimeSeconds local fade = 1 - (decayTime / decayDuration) fade = math.clamp(fade, 0, 1) -- πŸŒͺ️ STOVEPipe TRANSITION (late life) local decayProgress = math.clamp((aliveTime - lifetimeSeconds) / decayDuration, 0, 1) if decayProgress > 0.6 then -- stovepipe phase: tight, vertical, uniform column local pipeWidth = math.max(ef.sizeXZ * 0.25, 1.5) local pipeHeight = math.max(mesh.Scale.Y * 0.995, 10) mesh.Scale = Vector3.new( pipeWidth, pipeHeight, pipeWidth ) else -- normal decay rope-out mesh.Scale = Vector3.new( math.max(mesh.Scale.X * 0.992, 0.4), math.max(mesh.Scale.Y * 0.985, 8), math.max(mesh.Scale.Z * 0.992, 0.4) ) end currentSpeed *= 0.992 rotationStrength *= 0.992 smoke.Rate *= 0.985 wallEmitter.Rate *= 0.982 tornado.Transparency = math.clamp(tornado.Transparency + 0.003, 0, 1) wallCloud.Transparency = math.clamp(wallCloud.Transparency + 0.002, 0, 1) tornadoSound.Volume *= 0.985 closeWindSound.Volume *= 0.985 hailSound.Volume *= 0.985 Lighting.FogEnd = 5000 + ((1 - fade) * 35000) cc.Brightness = -0.08 * fade atmosphere.Density = 0.26 * fade if supercell and supercell.PrimaryPart then supercell:SetPrimaryPartCFrame( supercell.PrimaryPart.CFrame * CFrame.new(0, -0.15, 0) ) end if fade <= 0.01 then stormDead = true print("πŸŒͺ️ Tornado dissipated") break end end task.wait(0.1) end ------------------------------------------------ -- FINAL FADE OUT (TORNADO CLEANUP) ------------------------------------------------ for i = 1, 100 do local alpha = i / 100 if tornado then tornado.Transparency = alpha end if wallCloud then wallCloud.Transparency = alpha end if tornadoSound then tornadoSound.Volume = 1 - alpha end task.wait(0.03) end ------------------------------------------------ -- DESTROY CORE STORM OBJECTS ------------------------------------------------ if tornado then tornado:Destroy() end if smokePart then smokePart:Destroy() end if wallCloud then wallCloud:Destroy() end if tornadoSound then tornadoSound:Destroy() end if closeWindSound then closeWindSound:Destroy() end if hailSound then hailSound:Destroy() end if thunderSound then thunderSound:Destroy() end if gui then gui:Destroy() end ------------------------------------------------ -- START POST-DEATH FADE PHASE ------------------------------------------------ postDeathFade = true postFadeStart = tick() ------------------------------------------------ -- WAIT FOR POST FADE TO END ------------------------------------------------ task.spawn(function() while postDeathFade do task.wait(0.1) local t = math.clamp((tick() - postFadeStart) / postFadeDuration, 0, 1) ------------------------------------------------ -- πŸŒͺ️ SUPERCELL FADE (ONLY NOW) ------------------------------------------------ if supercell then for _, obj in ipairs(supercell:GetDescendants()) do if obj:IsA("BasePart") then obj.Transparency = math.clamp(obj.Transparency + 0.02, 0, 1) elseif obj:IsA("Decal") or obj:IsA("Texture") then obj.Transparency = math.clamp(obj.Transparency + 0.02, 0, 1) end end if supercell.PrimaryPart then supercell:SetPrimaryPartCFrame( supercell.PrimaryPart.CFrame * CFrame.new(0, -0.1, 0) ) end if t >= 1 then supercell:Destroy() supercell = nil end end ------------------------------------------------ -- 🌫️ SMOOTH ATMOSPHERE RECOVERY ------------------------------------------------ Lighting.Brightness = 1 + (2 - 1) * t local r = 40 + (128 - 40) * t local g = 40 + (128 - 40) * t local b = 45 + (128 - 45) * t Lighting.Ambient = Color3.fromRGB(r, g, b) Lighting.OutdoorAmbient = Lighting.Ambient Lighting.FogEnd = 5000 + (100000 - 5000) * t if atmosphere then atmosphere.Density = 0.26 * (1 - t) end if cc then cc.Brightness = -0.08 * (1 - t) cc.Contrast = 0.25 * (1 - t) cc.Saturation = -0.3 * (1 - t) end if t >= 1 then postDeathFade = false print("🌀️ Storm fully recovered") end end end) ---------------------------------------------------- -- FINAL CLEANUP (HARD SHUTDOWN) ---------------------------------------------------- task.spawn(function() -- wait until both core objects are gone repeat task.wait(1) until (not tornado or not tornado.Parent) and (not supercell or not supercell.Parent) ------------------------------------------------ -- πŸ”΄ HARD STOP ALL SYSTEMS ------------------------------------------------ stormDestroyed = true -- stop physics influence immediately currentSpeed = 0 rotationStrength = 0 angularVelocity = 0 ------------------------------------------------ -- STOP ALL SOUNDS ------------------------------------------------ if globalWindSound then globalWindSound:Stop() globalWindSound:Destroy() globalWindSound = nil end if tornadoSound then tornadoSound:Stop() tornadoSound:Destroy() tornadoSound = nil end if closeWindSound then closeWindSound:Stop() closeWindSound:Destroy() closeWindSound = nil end if hailSound then hailSound:Stop() hailSound:Destroy() hailSound = nil end if thunderSound then thunderSound:Stop() thunderSound:Destroy() thunderSound = nil end ------------------------------------------------ -- REMOVE VISUAL EFFECTS ------------------------------------------------ if cc then cc:Destroy() cc = nil end if atmosphere then atmosphere:Destroy() atmosphere = nil end local sky = Lighting:FindFirstChild("StormSky") if sky then sky:Destroy() end ------------------------------------------------ -- RESET LIGHTING BACK TO NORMAL ------------------------------------------------ Lighting.GlobalShadows = true Lighting.Ambient = Color3.fromRGB(0,0,0) Lighting.OutdoorAmbient = Color3.fromRGB(128,128,128) Lighting.FogColor = Color3.fromRGB(192, 192, 192) ------------------------------------------------ -- REMOVE UI ------------------------------------------------ if gui then gui:Destroy() gui = nil end ------------------------------------------------ -- REMOVE WORLD OBJECTS ------------------------------------------------ if tornado then tornado:Destroy() tornado = nil end if smokePart then smokePart:Destroy() smokePart = nil end if wallCloud then wallCloud:Destroy() wallCloud = nil end if supercell then supercell:Destroy() supercell = nil end ------------------------------------------------ -- FINAL CONFIRMATION ------------------------------------------------ print("🧹 STORM FULLY TERMINATED - NO LINGERING FORCE") end) end) ---------------------------------------------------- -- FORMATION (WEDGE ONLY) ---------------------------------------------------- task.spawn(function() currentStage = STAGE.WEDGE -- πŸŒͺ️ WEDGE BUILD (wide base, slow vertical stretch) for i = 1, 120 do local t = i / 120 -- BIG base, slow height increase local width = 2.5 + (t * 2.0) local height = 25 + (t * 90) mesh.Scale = Vector3.new( width, height, width ) -- dense low cloud = wedge look smoke.Rate = 30 + (t * 80) wallEmitter.Rate = 20 + (t * 60) -- keep storm β€œlow and heavy” tornado.Transparency = 0.25 task.wait(FUNNEL_TIME / 120) end funnelDone = true currentStage = STAGE.WEDGE -- πŸŒͺ️ WEDGE MAINTENANCE (DO NOT NARROW) for i = 1, 200 do local t = i / 200 local ease = t * t -- IMPORTANT: keep base wider than top always local baseWidth = ef.sizeXZ * (0.9 + ease * 0.4) local topWidth = baseWidth * 0.85 mesh.Scale = Vector3.new( baseWidth, ef.sizeY * (0.35 + ease * 0.65), baseWidth ) smoke.Rate = 200 + (ease * 250) wallEmitter.Rate = 120 + (ease * 300) -- keep wedge dense and low wallCloud.Transparency = 0.15 tornado.Transparency = 0.15 task.wait(0.1) end tornadoFullyFormed = true end) ------------------------------------------------ -- SCREEN SHAKE (EF-BASED STUDS SYSTEM) ------------------------------------------------ local EF_SHAKE = { [0] = { radius = 300, intensity = 0.2 }, [1] = { radius = 800, intensity = 0.4 }, [2] = { radius = 1400, intensity = 0.7 }, [3] = { radius = 2000, intensity = 1.0 }, [4] = { radius = 2800, intensity = 1.4 }, [5] = { radius = 4000, intensity = 2.0 } } RunService.RenderStepped:Connect(function() local char = Player.Character if not char then return end local hrp = char:FindFirstChild("HumanoidRootPart") if not hrp then return end if not tornado or not tornado.Parent then return end local dist = (hrp.Position - tornado.Position).Magnitude local cfg = EF_SHAKE[EF_LEVEL] or EF_SHAKE[3] ------------------------------------------------ -- EF-based shake radius check ------------------------------------------------ if dist <= cfg.radius then local intensity = (1 - (dist / cfg.radius)) * cfg.intensity local offset = Vector3.new( (math.random() - 0.5) * intensity, (math.random() - 0.5) * intensity, (math.random() - 0.5) * intensity ) camera.CFrame = camera.CFrame * CFrame.new(offset) end end) ---------------------------------------------------- -- MOVEMENT ---------------------------------------------------- local direction = Vector3.new(1,0,0) local targetPosition = spawnOrigin task.spawn(function() while true do task.wait(150) targetPosition = Vector3.new( math.random(-MAP_RADIUS,MAP_RADIUS), tornado.Position.Y, math.random(-MAP_RADIUS,MAP_RADIUS) ) end end) RunService.Heartbeat:Connect(function(dt) angularVelocity += rotationStrength * 0.002 local toTarget = safeUnit(targetPosition - tornado.Position) direction = safeUnit(direction:Lerp(toTarget,0.03)) local movement = direction * currentSpeed tornado.Position += movement * dt tornado.CFrame = CFrame.new(tornado.Position) * CFrame.Angles(0,angularVelocity,0) smokePart.Position = tornado.Position wallSpin += dt * 0.4 local wallRadius = ef.sizeXZ * 2.5 local spinOffset = Vector3.new( math.cos(wallSpin) * wallRadius, 0, math.sin(wallSpin) * wallRadius ) wallCloud.Position = tornado.Position + Vector3.new(0,90,0) + spinOffset if supercell and supercell.PrimaryPart then supercell:SetPrimaryPartCFrame( CFrame.new( tornado.Position + cellOffset ) ) end end) ---------------------------------------------------- -- PLAYER WIND + SFX ---------------------------------------------------- RunService.RenderStepped:Connect(function() local char = Player.Character if not char then return end local hrp = char:FindFirstChild("HumanoidRootPart") if not hrp then return end local dist = (hrp.Position - tornado.Position).Magnitude -- ONLY DARKNESS/ATMOSPHERE LASTS TO 42K if dist > 42000 and stormEnabled then stormEnabled = false if atmosphere then atmosphere.Parent = nil end if cc then cc.Parent = nil end Lighting.Ambient = Color3.fromRGB(128,128,128) Lighting.OutdoorAmbient = Color3.fromRGB(128,128,128) Lighting.FogEnd = 100000 Lighting.Brightness = 2 elseif dist <= 10000 and not stormEnabled then stormEnabled = true if atmosphere then atmosphere.Parent = Lighting end if cc then cc.Parent = Lighting end Lighting.Ambient = Color3.fromRGB(40,40,45) Lighting.OutdoorAmbient = Color3.fromRGB(55,55,60) Lighting.FogEnd = 5000 Lighting.Brightness = 1 end ---------------------------------------------------- -- ❄️ PRE-TORNADO / FORMATION PHYSICS ---------------------------------------------------- if not tornadoFullyFormed then -- EF0 SAFETY: no pull at all if EF_LEVEL ~= 0 and dist < 2500 then hrp.AssemblyLinearVelocity += safeUnit(tornado.Position - hrp.Position) * 2 end return end ---------------------------------------------------- -- πŸŒͺ️ FULL WIND SYSTEM ---------------------------------------------------- local wind = Vector3.new(0,0,0) if EF_LEVEL ~= 0 then wind = windField(hrp.Position) end local edgeBoost = math.clamp(1 - dist/OUTER_RADIUS, 0, 1)^3 -- apply force ONLY if EF > 0 if EF_LEVEL ~= 0 then hrp.AssemblyLinearVelocity += wind * (0.08 + edgeBoost * 0.25) end smoke.Rate = 200 + (800 * edgeBoost) ---------------------------------------------------- -- πŸ”Š SOUND ZONES ---------------------------------------------------- if dist < 7000 and not tornadoSound.IsPlaying then tornadoSound:Play() elseif dist >= 7000 then tornadoSound:Stop() end if dist < 500 and not closeWindSound.IsPlaying then closeWindSound:Play() elseif dist >= 500 then closeWindSound:Stop() end end) ---------------------------------------------------- -- HAIL ---------------------------------------------------- RunService.RenderStepped:Connect(function() local char = Player.Character if not char then return end local hrp = char:FindFirstChild("HumanoidRootPart") if not hrp then return end local dist = (hrp.Position - tornado.Position).Magnitude if dist < 42000 then if not hailSound.IsPlaying then hailSound:Play() end else hailSound:Stop() end end) print("πŸŒͺ️ STORM ACTIVE")