--!nocheck -- Lightning Strike v3 on the v2 stage (run stage.luau first), built from effects/_reference/lightning-v2.luau: -- gather and pre-glow, a two-frame flickering bolt, a thin echo, skidding sparks, thinning smoke, a lolling slump. local old = workspace:FindFirstChild("LabFX") if old then old:Destroy() end local og = game.StarterGui:FindFirstChild("LabFXOverlay") if og then og:Destroy() end local root = Instance.new("Folder") root.Name = "LabFX" root.Parent = workspace local PAPER = Color3.fromRGB(250, 246, 237) local MINT = Color3.fromRGB(35, 232, 162) local GLOW = Color3.fromRGB(200, 240, 255) local EMBER = Color3.fromRGB(255, 178, 110) local victim = workspace.LabStage.Victim local hrp, head = victim.HumanoidRootPart, victim.Head local hit = head.Position + Vector3.new(0, 0.6, 0) local feet = Vector3.new(hrp.Position.X, 0.2, hrp.Position.Z) local function part(props) local p = Instance.new("Part") p.Anchored = true p.CanCollide = false p.CastShadow = false for k, v in pairs(props) do p[k] = v end p.Parent = root return p end -- Bolts: one channel helper, three renders. A and B share endpoints with different jitter (the flicker), E is the echo. local function channel(list, rng, from, to, n, width, jitter, depth) local pts = { from } for i = 1, n - 1 do local base = from:Lerp(to, i / n) pts[#pts + 1] = base + Vector3.new(rng:NextNumber(-jitter, jitter), rng:NextNumber(-jitter * 0.4, jitter * 0.4), rng:NextNumber(-jitter, jitter)) end pts[#pts + 1] = to for i = 1, #pts - 1 do local a, b = pts[i], pts[i + 1] local len = (b - a).Magnitude local cf = CFrame.lookAt((a + b) / 2, b) local core = part({ Size = Vector3.new(width, width, len + width * 0.6), CFrame = cf, Material = Enum.Material.Neon, Color = PAPER, Transparency = 1 }) local glow = part({ Size = Vector3.new(width * 3, width * 3, len), CFrame = cf, Material = Enum.Material.Neon, Color = GLOW, Transparency = 1 }) list[#list + 1] = { core = core, glow = glow } if depth > 0 and i % 4 == 2 and i < #pts - 2 then local dir = (b - a).Unit local side = Vector3.new(rng:NextNumber(-1, 1), -0.5, rng:NextNumber(-1, 1)).Unit channel(list, rng, b, b + (dir * 0.4 + side).Unit * rng:NextNumber(6, 11), 5, width * 0.55, jitter * 0.7, depth - 1) end end end local top = hit + Vector3.new(-7, 58, -6) local boltA, boltB, boltE = {}, {}, {} channel(boltA, Random.new(11), top, hit, 22, 0.14, 3.2, 1) channel(boltB, Random.new(29), top, hit, 22, 0.14, 3.4, 1) channel(boltE, Random.new(47), top + Vector3.new(5, 0, 3), hit, 20, 0.08, 2.6, 0) local function showBolt(list, core, glow) for _, s in ipairs(list) do s.core.Transparency = core s.glow.Transparency = glow end end -- Lights: 4 at the hit, 1.2 in the sky. The sky light is what "gathers" before the strike. local function light(pos, range, shadows) local lp = part({ Size = Vector3.one, Transparency = 1, CFrame = CFrame.new(pos) }) local l = Instance.new("PointLight") l.Color = GLOW l.Range = range l.Shadows = shadows l.Brightness = 0 l.Parent = lp return l end local pl = light(hit, 36, true) local pl2 = light(hit + Vector3.new(-3, 26, -3), 60, false) local plFloor = light(feet + Vector3.new(0, 0.8, 0), 14, false) plFloor.Color = EMBER -- Scorch: matte dark disc and 14 short radial cracks (2 to 4 studs), no glow. local scorch = part({ Shape = Enum.PartType.Cylinder, Size = Vector3.new(0.05, 0.1, 0.1), CFrame = CFrame.new(feet.X, 0.23, feet.Z) * CFrame.Angles(0, 0, math.rad(90)), Material = Enum.Material.Slate, Color = Color3.fromRGB(22, 20, 19), Transparency = 0.15 }) local crng = Random.new(5) local cracks = {} for i = 1, 14 do cracks[i] = { p = part({ Size = Vector3.new(0.1, 0.06, 0.1), Material = Enum.Material.Slate, Color = Color3.fromRGB(16, 15, 14) }), a = i / 14 * math.pi * 2 + crng:NextNumber(-0.18, 0.18), len = crng:NextNumber(2, 4), r0 = crng:NextNumber(0.4, 1.1), w = crng:NextNumber(0.07, 0.13), } end -- Skidding sparks: 12 thin hot slivers that slide out along the floor from the feet and cool as they slow. local srng = Random.new(3) local skids = {} for i = 1, 12 do skids[i] = { p = part({ Size = Vector3.new(0.07, 0.07, 0.5), Material = Enum.Material.Neon, Color = PAPER, Transparency = 1 }), a = srng:NextNumber(0, math.pi * 2), dist = srng:NextNumber(3.5, 7.5), life = srng:NextNumber(0.35, 0.55), } end -- Smoke and burst sparks: engine textures, modest rates. local emitAt = part({ Size = Vector3.new(1.4, 2, 1), Transparency = 1, CFrame = CFrame.new(hrp.Position) }) local smoke = Instance.new("ParticleEmitter") smoke.Texture = "rbxasset://textures/particles/smoke_main.dds" smoke.Color = ColorSequence.new(Color3.fromRGB(118, 114, 110), Color3.fromRGB(178, 174, 168)) smoke.Size = NumberSequence.new({ NumberSequenceKeypoint.new(0, 1.6), NumberSequenceKeypoint.new(1, 4.6) }) smoke.Lifetime = NumberRange.new(1.2, 1.8) smoke.Rate = 0 smoke.Speed = NumberRange.new(1.5, 3) smoke.Acceleration = Vector3.new(0, 2.5, 0) smoke.SpreadAngle = Vector2.new(20, 20) smoke.Rotation = NumberRange.new(0, 360) smoke.RotSpeed = NumberRange.new(-30, 30) smoke.Parent = emitAt local function smokeAlpha(a) smoke.Transparency = NumberSequence.new({ NumberSequenceKeypoint.new(0, a), NumberSequenceKeypoint.new(1, 1) }) end smokeAlpha(0.3) local sparkAt = part({ Size = Vector3.new(0.5, 0.2, 0.5), Transparency = 1, CFrame = CFrame.new(feet + Vector3.new(0, 0.3, 0)) }) local sparks = Instance.new("ParticleEmitter") sparks.Texture = "rbxasset://textures/particles/sparkles_main.dds" sparks.Color = ColorSequence.new(PAPER, EMBER) sparks.Size = NumberSequence.new({ NumberSequenceKeypoint.new(0, 0.3), NumberSequenceKeypoint.new(1, 0) }) sparks.Lifetime = NumberRange.new(0.3, 0.7) sparks.Rate = 0 sparks.Speed = NumberRange.new(10, 20) sparks.SpreadAngle = Vector2.new(80, 80) sparks.Acceleration = Vector3.new(0, -40, 0) sparks.LightEmission = 1 sparks.Drag = 3 sparks.Parent = sparkAt -- The rig: remember rest state so step(t) poses it from rest and cleanup restores it. local bodyParts, byName = {}, {} for _, d in ipairs(victim:GetDescendants()) do if d:IsA("BasePart") and d.Name ~= "HumanoidRootPart" then local b = { p = d, color = d.Color, cf = d.CFrame, mat = d.Material } bodyParts[#bodyParts + 1] = b byName[d.Name] = b end end local neck = head.Position - Vector3.new(0, head.Size.Y / 2, 0) -- Hairline mint cracks (0.04 wide) riding on the body parts, so they follow the slump. local brng = Random.new(17) local bodyCracks = {} for _, n in ipairs({ "UpperTorso", "UpperTorso", "UpperTorso", "LowerTorso", "Head", "LeftUpperArm", "RightUpperArm", "LeftUpperLeg", "RightUpperLeg" }) do local b = byName[n] if b then local s = b.p.Size bodyCracks[#bodyCracks + 1] = { b = b, off = CFrame.new(brng:NextNumber(-0.3, 0.3) * s.X, brng:NextNumber(-0.3, 0.3) * s.Y, -s.Z / 2 - 0.01) * CFrame.Angles(0, 0, brng:NextNumber(-0.9, 0.9)), p = part({ Size = Vector3.new(0.04, brng:NextNumber(0.35, 0.8), 0.03), Material = Enum.Material.Neon, Color = MINT, Transparency = 1 }), } end end -- Overlay: one 80%-transparent flash frame and a small banner with a dark stroke, placed high. local gui = Instance.new("ScreenGui") gui.Name = "LabFXOverlay" gui.IgnoreGuiInset = true gui.Parent = game.StarterGui local flash = Instance.new("Frame") flash.Size = UDim2.fromScale(1, 1) flash.BackgroundColor3 = PAPER flash.BackgroundTransparency = 1 flash.BorderSizePixel = 0 flash.Parent = gui local function label(text, size, color, y) local t = Instance.new("TextLabel") t.BackgroundTransparency = 1 t.Text = text t.Font = Enum.Font.Oswald t.TextSize = size t.TextColor3 = color t.TextStrokeColor3 = Color3.fromRGB(27, 25, 24) t.TextStrokeTransparency = 0.35 t.AutomaticSize = Enum.AutomaticSize.XY t.Position = UDim2.new(0.5, 0, y, 0) t.AnchorPoint = Vector2.new(0.5, 0.5) t.TextTransparency = 1 t.Parent = gui return t end local banner = label("ZAPPED", 72, PAPER, 0.15) local sub = label("+200", 34, MINT, 0.225) local cam = workspace.CurrentCamera local camCF = CFrame.lookAt(hrp.Position + Vector3.new(9, 4.5, 11), hrp.Position + Vector3.new(0, 2.5, 0)) -- T = impact. With 12 frames over 1.6 s (every 0.145 s): 0.29 pre-glow, 0.44 bolt A + flash, 0.58 bolt B, 0.73 echo. local DUR, T = 1.6, 0.42 local function smooth(x) x = math.clamp(x, 0, 1) return x * x * (3 - 2 * x) end _G.LabFX = { duration = DUR, step = function(t) cam.CameraType = Enum.CameraType.Scriptable cam.CFrame = camCF local dt = t - T local pre = math.clamp(t / T, 0, 1) local post = math.clamp(dt / (DUR - T), 0, 1) -- Bolts: A for [T, T+0.09), B for [T+0.09, T+0.2), echo for [T+0.2, T+0.36). for _, l in ipairs({ boltA, boltB, boltE }) do showBolt(l, 1, 1) end if dt >= 0 and dt < 0.09 then showBolt(boltA, 0, 0.86) elseif dt >= 0.09 and dt < 0.2 then showBolt(boltB, 0.15 + (dt - 0.09) * 2, 0.88) elseif dt >= 0.2 and dt < 0.36 then showBolt(boltE, 0.1 + (dt - 0.2) * 3, 0.9) end -- Lights: sky gathers with a nervous flicker, spikes on each bolt, dies; an ember glow lingers at the feet. if dt < 0 then pl2.Brightness = 1.2 * pre * pre * (0.75 + 0.25 * math.sin(t * 55)) pl.Brightness = dt >= -0.14 and 2.2 or 0.6 * pre * pre plFloor.Brightness = 0 else local e = (dt >= 0.2 and dt < 0.36) and 2.4 * (1 - (dt - 0.2) / 0.16) or 0 pl.Brightness = math.max(dt < 0.2 and 4 * (1 - dt / 0.3) or 0, e, 0.3) pl2.Brightness = dt < 0.2 and 1.2 or (dt < 0.36 and 0.6 or 0) plFloor.Brightness = 1.2 * (1 - math.clamp(dt / 0.9, 0, 0.8)) end flash.BackgroundTransparency = (dt >= 0 and dt < 0.05) and 0.8 or 1 -- Scorch disc and cracks. local sr = dt < 0 and 0.1 or 0.1 + smooth(dt / 0.5) * 6.5 scorch.Size = Vector3.new(0.05, sr, sr) scorch.Transparency = dt < 0 and 1 or 0.15 for _, c in ipairs(cracks) do local len = dt < 0 and 0.05 or math.max(0.05, c.len * smooth(dt / 0.35)) c.p.Size = Vector3.new(c.w, 0.06, len) c.p.Transparency = dt < 0 and 1 or 0 c.p.CFrame = CFrame.new(feet.X, 0.255, feet.Z) * CFrame.Angles(0, c.a, 0) * CFrame.new(0, 0, -c.r0 - len / 2) end -- Skidding sparks: decelerating slide, shrinking streak, white-hot cooling to ember. for _, s in ipairs(skids) do local k = dt < 0 and -1 or dt / s.life if k < 0 or k > 1 then s.p.Transparency = 1 else local r = 0.6 + s.dist * (1 - (1 - k) ^ 2) local dir = Vector3.new(math.cos(s.a), 0, math.sin(s.a)) local pos = feet + dir * r + Vector3.new(0, 0.1, 0) s.p.Size = Vector3.new(0.07, 0.07, 0.15 + 0.9 * (1 - k)) s.p.CFrame = CFrame.lookAt(pos, pos + dir) s.p.Color = PAPER:Lerp(EMBER, k) s.p.Transparency = k * 0.7 end end if dt >= 0 and dt < 0.1 then sparks:Emit(60) end -- Smoke: rises from the body after impact, thins over the last half second. local thin = smooth((t - (DUR - 0.5)) / 0.5) smoke.Rate = dt >= 0.05 and 24 * (1 - thin * 0.8) or 0 smokeAlpha(0.3 + thin * 0.55) if dt >= 0.05 then smoke:Emit(math.floor(7 * (1 - thin * 0.7))) end emitAt.CFrame = CFrame.new((hrp.Position + Vector3.new(0, 1.5, 0)):Lerp(feet + Vector3.new(0, 1.2, 0), smooth((dt - 0.35) / 0.55))) -- Body: chars to Slate on impact, slumps from the feet, head keeps lolling about the neck. local burn = dt < 0 and 0 or 1 local s2 = smooth((dt - 0.35) / 0.55) local pivot = CFrame.new(feet.X, 0.3, feet.Z) local slumpCF = pivot * CFrame.Angles(math.rad(-78) * s2, 0, math.rad(12) * s2) * pivot:Inverse() local lollAmt = smooth((dt - 0.25) / 0.4) local loll = CFrame.new(neck) * CFrame.Angles(math.rad(-28) * lollAmt, 0, math.rad(24) * lollAmt * math.sin(math.max(dt, 0) * 6.5)) * CFrame.new(-neck) for _, b in ipairs(bodyParts) do b.p.Color = b.color:Lerp(Color3.fromRGB(24, 22, 21), burn * 0.9) b.p.Material = burn > 0 and Enum.Material.Slate or b.mat b.p.CFrame = b.p == head and slumpCF * loll * b.cf or slumpCF * b.cf end -- Hairline cracks: one frame of pre-glow before the strike, bright on impact, then cool. for _, c in ipairs(bodyCracks) do c.p.CFrame = c.b.p.CFrame * c.off if dt < -0.14 then c.p.Transparency = 1 elseif dt < 0 then c.p.Transparency = 0.35 else c.p.Transparency = 0.05 + math.min(1, post * 1.4) * 0.75 end end local pop = math.clamp(dt / 0.2, 0, 1) banner.TextTransparency = 1 - pop banner.TextSize = 56 + 16 * pop sub.TextTransparency = 1 - math.clamp((dt - 0.15) / 0.25, 0, 1) return t end, cleanup = function() for _, b in ipairs(bodyParts) do b.p.Color = b.color b.p.CFrame = b.cf b.p.Material = b.mat end local r = workspace:FindFirstChild("LabFX") if r then r:Destroy() end local g = game.StarterGui:FindFirstChild("LabFXOverlay") if g then g:Destroy() end _G.LabFX = nil end, } _G.LabFX.step(0) return { built = #root:GetDescendants(), duration = DUR }