local Players = game:GetService("Players") local TweenService = game:GetService("TweenService") local Debris = game:GetService("Debris") -- SETTINGS local OOF_SOUND_ID = "rbxassetid://6029715808" -- swap for any oof sound ID if this one doesn't play local ACCESSORY_DROP_HEALTH = 50 local MAX_BRUISES_PER_LIMB = 6 local RED_COLOR = Color3.fromRGB(255, 0, 0) local RED_FADE_TIME = 1.2 local HIT_MEMORY = 0.5 local LIMBS = { "Head", "Torso", "Left Arm", "Right Arm", "Left Leg", "Right Leg" } local BLOOD_COLORS = { Color3.fromRGB(120, 0, 0), Color3.fromRGB(150, 0, 0), Color3.fromRGB(90, 0, 0), } -- Joint limits (degrees) for a more natural ragdoll local JOINT_LIMITS = { ["Neck"] = { upper = 35, twist = 30 }, ["Left Shoulder"] = { upper = 110, twist = 40 }, ["Right Shoulder"] = { upper = 110, twist = 40 }, ["Left Hip"] = { upper = 75, twist = 15 }, ["Right Hip"] = { upper = 75, twist = 15 }, } local bloodFolder = Instance.new("Folder") bloodFolder.Name = "BloodEffects" bloodFolder.Parent = workspace ---------------------------------------------------------------- -- R15 warning ---------------------------------------------------------------- local function showR15Message(player) local gui = Instance.new("ScreenGui") gui.Name = "R6Warning" gui.ResetOnSpawn = false gui.Parent = player:WaitForChild("PlayerGui") local label = Instance.new("TextLabel") label.Size = UDim2.new(0.6, 0, 0.1, 0) label.Position = UDim2.new(0.2, 0, 0.4, 0) label.BackgroundTransparency = 1 label.Text = "r6 dude why u r15" label.TextScaled = true label.Font = Enum.Font.GothamBold label.TextColor3 = Color3.fromRGB(255, 60, 60) label.TextStrokeTransparency = 0 label.Parent = gui Debris:AddItem(gui, 3) end ---------------------------------------------------------------- -- Blood ---------------------------------------------------------------- local function makePool(position, damage) local params = RaycastParams.new() params.FilterDescendantsInstances = { bloodFolder } params.FilterType = Enum.RaycastFilterType.Exclude local result = workspace:Raycast(position, Vector3.new(0, -15, 0), params) if not result then return end local hitModel = result.Instance:FindFirstAncestorOfClass("Model") if hitModel and hitModel:FindFirstChildOfClass("Humanoid") then return end local diameter = math.clamp(damage / 4, 2, 12) local pool = Instance.new("Part") pool.Name = "BloodPool" pool.Shape = Enum.PartType.Cylinder pool.Anchored = true pool.CanCollide = false pool.CanQuery = false pool.CanTouch = false pool.Material = Enum.Material.SmoothPlastic pool.Color = Color3.fromRGB(110, 0, 0) pool.Reflectance = 0.15 pool.Size = Vector3.new(0.05, 0.2, 0.2) pool.CFrame = CFrame.new(result.Position + Vector3.new(0, 0.04, 0)) * CFrame.Angles(0, 0, math.pi / 2) pool.Parent = bloodFolder TweenService:Create(pool, TweenInfo.new(1.5, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), { Size = Vector3.new(0.05, diameter, diameter), }):Play() task.delay(18, function() if pool.Parent then local fade = TweenService:Create(pool, TweenInfo.new(3), { Transparency = 1 }) fade:Play() fade.Completed:Wait() pool:Destroy() end end) end local function makeSplat(position) local params = RaycastParams.new() params.FilterDescendantsInstances = { bloodFolder } params.FilterType = Enum.RaycastFilterType.Exclude local result = workspace:Raycast(position + Vector3.new(0, 1, 0), Vector3.new(0, -4, 0), params) if not result then return end local size = math.random(5, 14) / 10 local splat = Instance.new("Part") splat.Name = "BloodSplat" splat.Shape = Enum.PartType.Cylinder splat.Anchored = true splat.CanCollide = false splat.CanQuery = false splat.CanTouch = false splat.Material = Enum.Material.SmoothPlastic splat.Color = BLOOD_COLORS[math.random(#BLOOD_COLORS)] splat.Size = Vector3.new(0.04, size, size) splat.CFrame = CFrame.new(result.Position + Vector3.new(0, 0.03, 0)) * CFrame.Angles(0, 0, math.pi / 2) splat.Parent = bloodFolder Debris:AddItem(splat, 12) end local function spawnBlood(position, damage) local count = math.clamp(math.floor(damage / 1.5), 3, 70) local power = math.clamp(10 + damage * 0.4, 12, 55) for _ = 1, count do local drop = Instance.new("Part") drop.Name = "BloodDrop" local s = math.random(10, 35) / 100 drop.Size = Vector3.new(s, s, s) drop.Shape = Enum.PartType.Ball drop.Material = Enum.Material.SmoothPlastic drop.Color = BLOOD_COLORS[math.random(#BLOOD_COLORS)] drop.CanCollide = true drop.Massless = true drop.CFrame = CFrame.new(position) drop.Parent = bloodFolder local dir = Vector3.new(math.random(-100, 100) / 100, math.random(40, 120) / 100, math.random(-100, 100) / 100).Unit drop.AssemblyLinearVelocity = dir * math.random(power * 0.5, power) local landed = false drop.Touched:Connect(function(other) if landed then return end if other.Name == "BloodDrop" or other.Name == "BloodSplat" or other.Name == "BloodPool" then return end local model = other:FindFirstAncestorOfClass("Model") if model and model:FindFirstChildOfClass("Humanoid") then return end landed = true makeSplat(drop.Position) drop:Destroy() end) Debris:AddItem(drop, 5) end makePool(position, damage) end ---------------------------------------------------------------- -- Bruises ---------------------------------------------------------------- local BRUISE_FACES = { Enum.NormalId.Front, Enum.NormalId.Back, Enum.NormalId.Left, Enum.NormalId.Right } local BRUISE_CORES = { Color3.fromRGB(85, 30, 110), Color3.fromRGB(60, 35, 120), Color3.fromRGB(100, 30, 80), } local function makeCircle(parent, size, pos, color, transparency) local f = Instance.new("Frame") f.AnchorPoint = Vector2.new(0.5, 0.5) f.Size = UDim2.fromScale(size, size) f.Position = pos f.BackgroundColor3 = color f.BackgroundTransparency = transparency f.BorderSizePixel = 0 f.Parent = parent local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0.5, 0) corner.Parent = f end local function addBruise(limb, damage) local count = limb:GetAttribute("BruiseCount") or 0 if count >= MAX_BRUISES_PER_LIMB then return end limb:SetAttribute("BruiseCount", count + 1) local gui = Instance.new("SurfaceGui") gui.Name = "Bruise" gui.Face = BRUISE_FACES[math.random(#BRUISE_FACES)] gui.SizingMode = Enum.SurfaceGuiSizingMode.PixelsPerStud gui.PixelsPerStud = 50 gui.LightInfluence = 0.3 gui.Adornee = limb gui.Parent = limb local size = math.clamp(0.25 + damage / 80, 0.25, 0.6) local pos = UDim2.fromScale(math.random(30, 70) / 100, math.random(30, 70) / 100) makeCircle(gui, size * 1.45, pos, Color3.fromRGB(150, 150, 50), 0.7) -- yellow-green edge makeCircle(gui, size, pos, BRUISE_CORES[math.random(#BRUISE_CORES)], 0.4) makeCircle(gui, size * 0.55, pos, Color3.fromRGB(45, 15, 60), 0.3) -- dark center end ---------------------------------------------------------------- -- Oof + accessory drop ---------------------------------------------------------------- local function playOof(head) local sound = Instance.new("Sound") sound.SoundId = OOF_SOUND_ID sound.Volume = 1 sound.PlaybackSpeed = math.random(95, 110) / 100 sound.RollOffMaxDistance = 80 sound.Parent = head sound:Play() Debris:AddItem(sound, 3) end local function dropAccessory(character) local list = {} for _, acc in ipairs(character:GetChildren()) do if acc:IsA("Accessory") and acc:FindFirstChild("Handle") then table.insert(list, acc) end end if #list == 0 then return end local acc = list[math.random(#list)] local handle = acc.Handle for _, c in ipairs(handle:GetChildren()) do if c:IsA("Weld") or c:IsA("WeldConstraint") or c:IsA("Motor6D") then c:Destroy() end end acc.Parent = workspace handle.CanCollide = true handle.Massless = false handle.AssemblyLinearVelocity = Vector3.new(math.random(-12, 12), math.random(15, 25), math.random(-12, 12)) handle.AssemblyAngularVelocity = Vector3.new(math.random(-10, 10), math.random(-10, 10), math.random(-10, 10)) pcall(function() handle:SetNetworkOwner(nil) end) Debris:AddItem(acc, 30) end ---------------------------------------------------------------- -- Character setup ---------------------------------------------------------------- local function setupCharacter(player, character) local humanoid = character:WaitForChild("Humanoid") local root = character:WaitForChild("HumanoidRootPart") if humanoid.RigType ~= Enum.HumanoidRigType.R6 then showR15Message(player) return end local torso = character:WaitForChild("Torso") local head = character:WaitForChild("Head") -- Ragdoll joints (RootJoint stays on so the root follows the torso and the camera keeps up) local joints = {} for _, motor in ipairs(character:GetDescendants()) do if motor:IsA("Motor6D") and motor.Part0 and motor.Part1 and motor.Name ~= "RootJoint" then local a0 = Instance.new("Attachment") a0.Name = "RagdollAttachment0" a0.CFrame = motor.C0 a0.Parent = motor.Part0 local a1 = Instance.new("Attachment") a1.Name = "RagdollAttachment1" a1.CFrame = motor.C1 a1.Parent = motor.Part1 local limits = JOINT_LIMITS[motor.Name] or { upper = 60, twist = 30 } local socket = Instance.new("BallSocketConstraint") socket.Attachment0 = a0 socket.Attachment1 = a1 socket.LimitsEnabled = true socket.UpperAngle = limits.upper socket.TwistLimitsEnabled = true socket.TwistLowerAngle = -limits.twist socket.TwistUpperAngle = limits.twist socket.Restitution = 0 socket.Enabled = false socket.Parent = motor.Parent -- stop connected parts from fighting each other local ncc = Instance.new("NoCollisionConstraint") ncc.Part0 = motor.Part0 ncc.Part1 = motor.Part1 ncc.Parent = motor.Parent table.insert(joints, { motor = motor, socket = socket }) end end local originalCollide = {} local rootCollide = root.CanCollide local savedLook = Vector3.new(0, 0, -1) local ragdolled = false local ragdollToken = 0 local function ragdoll(duration, hitDir, damage) if not ragdolled then ragdolled = true local look = root.CFrame.LookVector local flat = Vector3.new(look.X, 0, look.Z) if flat.Magnitude > 0.1 then savedLook = flat.Unit end humanoid.AutoRotate = false humanoid.PlatformStand = true humanoid:SetStateEnabled(Enum.HumanoidStateType.GettingUp, false) for _, j in ipairs(joints) do j.motor.Enabled = false j.socket.Enabled = true end rootCollide = root.CanCollide root.CanCollide = false for _, part in ipairs(character:GetChildren()) do if part:IsA("BasePart") and part ~= root then originalCollide[part] = part.CanCollide part.CanCollide = true part.CustomPhysicalProperties = PhysicalProperties.new(0.7, 0.6, 0.1, 1, 1) end end humanoid:ChangeState(Enum.HumanoidStateType.Physics) end -- knockback away from whatever hit us, stronger with more damage local push = math.clamp(15 + damage * 0.8, 15, 70) local dir = hitDir or Vector3.new(math.random(-100, 100) / 100, 0, math.random(-100, 100) / 100) torso.AssemblyLinearVelocity = dir * push + Vector3.new(0, math.clamp(10 + damage * 0.3, 10, 35), 0) torso.AssemblyAngularVelocity = Vector3.new(math.random(-6, 6), math.random(-6, 6), math.random(-6, 6)) * math.clamp(damage / 15, 0.5, 3) ragdollToken += 1 local myToken = ragdollToken task.delay(duration, function() if myToken ~= ragdollToken then return end if not ragdolled or humanoid.Health <= 0 or not character.Parent then return end ragdolled = false -- stand the character back up where the body is lying local pos = torso.Position + Vector3.new(0, 1.5, 0) root.AssemblyLinearVelocity = Vector3.zero root.AssemblyAngularVelocity = Vector3.zero root.CFrame = CFrame.lookAt(pos, pos + savedLook) for _, j in ipairs(joints) do j.socket.Enabled = false j.motor.Enabled = true end for part, value in pairs(originalCollide) do if part.Parent then part.CanCollide = value part.CustomPhysicalProperties = nil end end table.clear(originalCollide) root.CanCollide = rootCollide humanoid:SetStateEnabled(Enum.HumanoidStateType.GettingUp, true) humanoid.PlatformStand = false humanoid.AutoRotate = true humanoid:ChangeState(Enum.HumanoidStateType.GettingUp) end) end -- Hit tracking + red flash local lastTouchedPart, lastTouchTime, lastHitPos = nil, 0, nil local originalColors = {} local redTokens = {} for _, name in ipairs(LIMBS) do local limb = character:FindFirstChild(name) if limb and limb:IsA("BasePart") then originalColors[limb] = limb.Color redTokens[limb] = 0 limb.Touched:Connect(function(other) if other:IsDescendantOf(character) then return end if other:IsDescendantOf(bloodFolder) then return end local model = other:FindFirstAncestorOfClass("Model") local isTool = other:FindFirstAncestorOfClass("Tool") ~= nil local isCharacter = model and model:FindFirstChildOfClass("Humanoid") ~= nil if isTool or isCharacter then lastTouchedPart = limb lastTouchTime = os.clock() lastHitPos = other.Position end end) end end local function flashRed(limb) redTokens[limb] += 1 local myToken = redTokens[limb] limb.Color = RED_COLOR task.delay(0.25, function() if redTokens[limb] == myToken and limb.Parent then TweenService:Create(limb, TweenInfo.new(RED_FADE_TIME), { Color = originalColors[limb] }):Play() end end) end local lastHealth = humanoid.Health humanoid.HealthChanged:Connect(function(newHealth) local damage = lastHealth - newHealth local previous = lastHealth lastHealth = newHealth if damage <= 0 then return end -- which limb was hit local hitPart local hitDir if lastTouchedPart and lastTouchedPart.Parent and os.clock() - lastTouchTime <= HIT_MEMORY then hitPart = lastTouchedPart if lastHitPos then local away = torso.Position - lastHitPos away = Vector3.new(away.X, 0, away.Z) if away.Magnitude > 0.1 then hitDir = away.Unit end end else hitPart = torso -- fallback (fall damage, scripted damage, etc.) end playOof(head) flashRed(hitPart) spawnBlood(hitPart.Position, damage) if damage >= 5 then local bruiseCount = damage < 15 and 1 or (damage < 35 and 2 or 3) for _ = 1, bruiseCount do addBruise(hitPart, damage) end end -- an accessory falls off when you drop to 50 health or below if previous > ACCESSORY_DROP_HEALTH and newHealth <= ACCESSORY_DROP_HEALTH and newHealth > 0 then dropAccessory(character) end if newHealth > 0 then local duration = math.clamp(1.5 + damage / 20, 1.5, 4) ragdoll(duration, hitDir, damage) end end) humanoid.Died:Connect(function() ragdollToken += 1 end) end local function onPlayerAdded(player) player.CharacterAdded:Connect(function(character) setupCharacter(player, character) end) if player.Character then setupCharacter(player, player.Character) end end Players.PlayerAdded:Connect(onPlayerAdded) for _, player in ipairs(Players:GetPlayers()) do onPlayerAdded(player) end