--- -- 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 = 200, wind = 80 }, [1] = { radius = 400, wind = 100 }, [2] = { radius = 500, wind = 130 }, [3] = { radius = 4089, wind = 155 }, [4] = { radius = 4300, wind = 185 }, [5] = { radius = 5982, wind = 250 }, } local INTERIOR_VISIBILITY = { [0] = 500, [1] = 559, [2] = 600, [3] = 5089, [4] = 5580, [5] = 6382, } local function getInteriorDistance() return INTERIOR_VISIBILITY[EF_LEVEL] or 4000 end --- -- πŸŒ₯️ 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 }, } --- -- 🌫️ ATMOSPHERE SYSTEM (EF-BASED STORM FOG) local EF_ATMOSPHERE = { [0] = { density = 0.08, 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 }, } --- -- ATMOSPHERE DISTANCE BY EF local EF_ATMOSPHERE_DISTANCE = { [0] = 80, [1] = 99, [2] = 100, [3] = 6828, [4] = 7000, [5] = 7298 } --- -- MAX DENSITY / HAZE local MAX_DENSITY = { [0] = 1.0, [1] = 1.5, [2] = 2.5, [3] = 4.0, [4] = 6.0, [5] = 8.0 } local MAX_HAZE = { [0] = 4, [1] = 8, [2] = 12, [3] = 20, [4] = 35, [5] = 50 } --- -- 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 + DISTANCE CONTROL) local function updateAtmosphere(fade) if not atmosphere then return end fade = fade or 1 local cfg = EF_ATMOSPHERE[EF_LEVEL] or EF_ATMOSPHERE[3] --- -- DISTANCE TO TORNADO local distFactor = 0 local char = Player.Character if char and char:FindFirstChild("HumanoidRootPart") and tornado and tornado.Parent then local dist = (char.HumanoidRootPart.Position - tornado.Position).Magnitude local maxDist = EF_ATMOSPHERE_DISTANCE[EF_LEVEL] or 4000 distFactor = math.clamp( 1 - (dist / maxDist), 0, 1 ) end --- -- DENSITY + HAZE atmosphere.Density = ( cfg.density + ( (MAX_DENSITY[EF_LEVEL] or 4) - cfg.density ) * distFactor ) * fade atmosphere.Haze = ( cfg.haze + ( (MAX_HAZE[EF_LEVEL] or 20) - cfg.haze ) * distFactor ) * fade --- -- EF-BASED COLOR 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) --- -- DECAY COLOR 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 --- -- 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, -- PLEASE DONT CHANGE THEM! [1] = 0, [2] = 0, [3] = 0, [4] = 0, [5] = 0 -- PLEASE DONT CHANGE THEM! } 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 > 42000 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 (FIXED) local INTERIOR_MODEL_ID = "rbxassetid://8612582067" local interiorModels = {} local MAX_MODELS = 150 local template = game:GetObjects(INTERIOR_MODEL_ID)[1] if not template then warn("Interior model failed to load") return end --- -- 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 if not m.PrimaryPart then m:Destroy() continue end table.insert(interiorModels, { model = m, angle = math.random() * math.pi * 2, radius = math.random(), height = math.random(), speed = 2 + math.random() }) end --- -- MOTION SYSTEM (PLAYER-BOUND + CLEANUP) local function getHRP() local char = Player.Character if not char then return nil end return char:FindFirstChild("HumanoidRootPart") end RunService.Heartbeat:Connect(function(dt) if stormDestroyed or not tornado or not tornado.Parent or #interiorModels == 0 then -- hard cleanup if storm dies for _, data in ipairs(interiorModels) do if data.model then data.model:Destroy() end end table.clear(interiorModels) return end local hrp = getHRP() if not hrp then return end local probe = PROBE_EF_TABLE[EF_LEVEL] local maxRadius = (probe and probe.radius) or 3000 --- -- UPDATE MODELS for i = #interiorModels, 1, -1 do local data = interiorModels[i] local m = data.model local hrp = getHRP() if not hrp then return end local distToStorm = (hrp.Position - tornado.Position).Magnitude local maxView = getInteriorDistance() local visible = distToStorm <= maxView for _, obj in ipairs(m:GetDescendants()) do if obj:IsA("ParticleEmitter") then obj.Enabled = visible elseif obj:IsA("PointLight") or obj:IsA("SurfaceLight") then obj.Enabled = visible end end if not m or not m.Parent or not m.PrimaryPart then table.remove(interiorModels, i) continue end -- update orbit data.angle += dt * data.speed * 2 ------------------------------------------------ -- πŸ”₯ FOLLOW PLAYER (NOT TORNADO) ------------------------------------------------ local center = hrp.Position local baseX = math.cos(data.angle) * (data.radius * maxRadius) local baseZ = math.sin(data.angle) * (data.radius * maxRadius) local worldPos = center + Vector3.new(baseX, 0, baseZ) ------------------------------------------------ -- 🌍 ground snap ------------------------------------------------ local origin = worldPos + Vector3.new(0, 500, 0) local result = workspace:Raycast(origin, Vector3.new(0, -2000, 0)) local groundY = worldPos.Y if result then groundY = result.Position.Y end local finalPos = Vector3.new(worldPos.X, groundY + 2, worldPos.Z) ------------------------------------------------ -- πŸ’€ DISTANCE CHECK (DESTROY IF OUT OF RANGE) ------------------------------------------------ local dist = (hrp.Position - m.PrimaryPart.Position).Magnitude if dist > maxRadius or not tornado or not tornado.Parent then m:Destroy() table.remove(interiorModels, i) continue end ------------------------------------------------ -- APPLY MOVEMENT ------------------------------------------------ m:PivotTo( CFrame.new(finalPos) * CFrame.Angles(0, data.angle * 3, 0) ) 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") Player.CharacterAdded:Connect(function() task.wait(0.2) if tool and tool.Parent ~= Player.Backpack then tool.Parent = Player.Backpack end end) 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(49) 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 <= 42000 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")