--// Servicios local TS = game:GetService("TweenService") local RunService = game:GetService("RunService") local Debris = game:GetService("Debris") local Players = game:GetService("Players") local CollectionService = game:GetService("CollectionService") local LocalPlayer = Players.LocalPlayer --// Tweens local tweenInfoIn = TweenInfo.new(0.2, Enum.EasingStyle.Exponential, Enum.EasingDirection.Out) local tweenInfoOut = TweenInfo.new(1.5, Enum.EasingStyle.Linear, Enum.EasingDirection.Out) local tweenSecado = TweenInfo.new(15, Enum.EasingStyle.Quad, Enum.EasingDirection.Out) --// Texturas (Decals) local bloodDecals = { "rbxassetid://9893638760", "rbxassetid://9893615086", "rbxassetid://9893636391" } --// Sonidos 3D local splatSounds = { "rbxassetid://9114223178", "rbxassetid://9114223280", "rbxassetid://9114223405" } local deathSoundId = "rbxassetid://9114223553" --// CONFIGURACIÓN local bloodSettings = { Duration = 30, MaxDecals = 200 } local bloodService = {} local Pool = {} local ActiveDrops = {} local ActiveDecalsTrack = {} local MAX_POOL_SIZE = 300 local newPartsFolder = workspace:FindFirstChild("BloodParticles") or Instance.new("Folder", workspace) newPartsFolder.Name = "BloodParticles" ------------------------------------------------------------------------------- -- UI: PANTALLA ENSANGRENTADA Y HUB ------------------------------------------------------------------------------- local screenGui = Instance.new("ScreenGui") screenGui.Name = "BloodScreenFX" screenGui.ResetOnSpawn = false if LocalPlayer then local pGui = LocalPlayer:FindFirstChildOfClass("PlayerGui") or LocalPlayer:WaitForChild("PlayerGui", 5) if pGui then screenGui.Parent = pGui end end local damageOverlay = Instance.new("Frame") damageOverlay.Size = UDim2.new(1, 0, 1, 0) damageOverlay.BackgroundColor3 = Color3.fromRGB(150, 0, 0) damageOverlay.BackgroundTransparency = 1 damageOverlay.BorderSizePixel = 0 damageOverlay.Parent = screenGui local function flashScreenDamage() damageOverlay.BackgroundTransparency = 0.65 TS:Create(damageOverlay, TweenInfo.new(0.3), {BackgroundTransparency = 1}):Play() end local function createSettingsHUB() local buttonContainer = Instance.new("Frame") buttonContainer.Name = "BloodButtonContainer" buttonContainer.Size = UDim2.new(0, 150, 0, 35) buttonContainer.Position = UDim2.new(0, 15, 0.5, -17) buttonContainer.BackgroundTransparency = 1 buttonContainer.Parent = screenGui local toggleBtn = Instance.new("TextButton") toggleBtn.Name = "BloodMenuToggle" toggleBtn.Size = UDim2.new(1, -25, 1, 0) toggleBtn.Position = UDim2.new(0, 0, 0, 0) toggleBtn.BackgroundColor3 = Color3.fromRGB(100, 10, 10) toggleBtn.TextColor3 = Color3.fromRGB(255, 255, 255) toggleBtn.Text = "🩸 Sangre: 30s" toggleBtn.Font = Enum.Font.SourceSansBold toggleBtn.TextSize = 14 toggleBtn.Parent = buttonContainer local toggleUICorner = Instance.new("UICorner") toggleUICorner.CornerRadius = UDim.new(0, 8) toggleUICorner.Parent = toggleBtn local hubFrame = Instance.new("Frame") hubFrame.Name = "BloodHUB" hubFrame.Size = UDim2.new(0, 160, 0, 240) hubFrame.Position = UDim2.new(0, 175, 0.5, -120) hubFrame.BackgroundColor3 = Color3.fromRGB(25, 25, 25) hubFrame.BackgroundTransparency = 0.1 hubFrame.Visible = false hubFrame.Parent = screenGui local closeBtn = Instance.new("TextButton") closeBtn.Name = "CloseUI" closeBtn.Size = UDim2.new(0, 20, 0, 35) closeBtn.Position = UDim2.new(1, -20, 0, 0) closeBtn.BackgroundColor3 = Color3.fromRGB(180, 30, 30) closeBtn.TextColor3 = Color3.fromRGB(255, 255, 255) closeBtn.Text = "X" closeBtn.Font = Enum.Font.SourceSansBold closeBtn.TextSize = 14 closeBtn.Parent = buttonContainer local closeCorner = Instance.new("UICorner") closeCorner.CornerRadius = UDim.new(0, 8) closeCorner.Parent = closeBtn closeBtn.MouseButton1Click:Connect(function() buttonContainer.Visible = false hubFrame.Visible = false end) local hubUICorner = Instance.new("UICorner") hubUICorner.CornerRadius = UDim.new(0, 8) hubUICorner.Parent = hubFrame local title = Instance.new("TextLabel") title.Size = UDim2.new(1, 0, 0, 30) title.BackgroundTransparency = 1 title.Text = "Duración de Sangre" title.TextColor3 = Color3.fromRGB(255, 255, 255) title.Font = Enum.Font.SourceSansBold title.TextSize = 14 title.Parent = hubFrame local listLayout = Instance.new("UIListLayout") listLayout.Padding = UDim.new(0, 5) listLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center listLayout.SortOrder = Enum.SortOrder.LayoutOrder listLayout.Parent = hubFrame title.LayoutOrder = 0 local timeOptions = { {text = "30 Segundos", time = 30, maxDecals = 200}, {text = "1 Minuto", time = 60, maxDecals = 200}, {text = "2 Minutos", time = 120, maxDecals = 200}, {text = "3 Minutos", time = 180, maxDecals = 200}, {text = "4 Minutos", time = 240, maxDecals = 200}, {text = "5 Minutos", time = 300, maxDecals = 400} } local buttons = {} for i, option in ipairs(timeOptions) do local btn = Instance.new("TextButton") btn.Size = UDim2.new(0.9, 0, 0, 28) btn.LayoutOrder = i btn.Text = option.text btn.Font = Enum.Font.SourceSans btn.TextSize = 13 btn.Parent = hubFrame local btnCorner = Instance.new("UICorner") btnCorner.CornerRadius = UDim.new(0, 5) btnCorner.Parent = btn local function updateVisuals() for _, bData in ipairs(buttons) do if bData.time == bloodSettings.Duration then bData.button.BackgroundColor3 = Color3.fromRGB(180, 20, 20) bData.button.TextColor3 = Color3.fromRGB(255, 255, 255) else bData.button.BackgroundColor3 = Color3.fromRGB(45, 45, 45) bData.button.TextColor3 = Color3.fromRGB(200, 200, 200) end end toggleBtn.Text = "🩸 Sangre: " .. (bloodSettings.Duration >= 60 and (bloodSettings.Duration / 60 .. "m") or (bloodSettings.Duration .. "s")) end btn.MouseButton1Click:Connect(function() bloodSettings.Duration = option.time bloodSettings.MaxDecals = option.maxDecals updateVisuals() end) table.insert(buttons, {button = btn, time = option.time}) end for _, bData in ipairs(buttons) do if bData.time == bloodSettings.Duration then bData.button.BackgroundColor3 = Color3.fromRGB(180, 20, 20) bData.button.TextColor3 = Color3.fromRGB(255, 255, 255) else bData.button.BackgroundColor3 = Color3.fromRGB(45, 45, 45) bData.button.TextColor3 = Color3.fromRGB(200, 200, 200) end end toggleBtn.MouseButton1Click:Connect(function() hubFrame.Visible = not hubFrame.Visible end) end createSettingsHUB() ------------------------------------------------------------------------------- -- POOLING & CREACIÓN DE PIEZAS ------------------------------------------------------------------------------- function createPart(): Part local p: Part = Instance.new("Part") p.Name = "BlPart" p.Shape = Enum.PartType.Block p.Size = Vector3.new(0.2, 0.2, 0.2) p.Transparency = 1 p.CanCollide = false p.Massless = true p.Material = Enum.Material.SmoothPlastic p.Anchored = false p.CastShadow = false local decal = Instance.new("Decal") decal.Name = "Splatter" decal.Face = Enum.NormalId.Top decal.Transparency = 1 decal.Parent = p local bgui = Instance.new("BillboardGui") bgui.Name = "FlyingImage" bgui.Size = UDim2.new(2.8, 0, 2.8, 0) bgui.AlwaysOnTop = true bgui.LightInfluence = 0.5 bgui.Parent = p local img = Instance.new("ImageLabel") img.Name = "Img" img.Size = UDim2.new(1, 0, 1, 0) img.BackgroundTransparency = 1 img.Parent = bgui return p end local function GetPart() if #Pool > 0 then return table.remove(Pool) end return createPart() end local function ReturnPart(part: Part) if not part then return end ActiveDrops[part] = nil part.Anchored = false part.Transparency = 1 part.CanCollide = false part.Massless = true part.Parent = nil part.Size = Vector3.new(0.2, 0.2, 0.2) part.AssemblyLinearVelocity = Vector3.zero part.AssemblyAngularVelocity = Vector3.zero -- Limpieza estricta de soldaduras (WeldConstraints) for _, child in ipairs(part:GetChildren()) do if child:IsA("WeldConstraint") then child:Destroy() end end local decal = part:FindFirstChild("Splatter") if decal then decal.Transparency = 1 decal.Color3 = Color3.new(1, 1, 1) end local bgui = part:FindFirstChild("FlyingImage") if bgui then bgui.Enabled = false end for _, tag in ipairs(CollectionService:GetTags(part)) do CollectionService:RemoveTag(part, tag) end if #Pool < MAX_POOL_SIZE then table.insert(Pool, part) else part:Destroy() end end local function RegisterNewDecal(part: Part) for i, p in ipairs(ActiveDecalsTrack) do if p == part then return end end table.insert(ActiveDecalsTrack, part) if #ActiveDecalsTrack > bloodSettings.MaxDecals then local oldestPart = table.remove(ActiveDecalsTrack, 1) if oldestPart and oldestPart.Parent then ReturnPart(oldestPart) end end end ------------------------------------------------------------------------------- -- EFECTOS VISUALES EXTRA ------------------------------------------------------------------------------- local function playSplatSound(position: Vector3) local soundPart = Instance.new("Part") soundPart.Size = Vector3.new(0.1, 0.1, 0.1) soundPart.Position = position soundPart.Anchored = true soundPart.Transparency = 1 soundPart.CanCollide = false soundPart.Parent = workspace local sound = Instance.new("Sound") sound.SoundId = splatSounds[math.random(1, #splatSounds)] sound.Volume = 0.7 sound.RollOffMaxDistance = 60 sound.Parent = soundPart sound:Play() Debris:AddItem(soundPart, 1.5) end local function createBloodMist(position: Vector3) local mistPart = Instance.new("Part") mistPart.Size = Vector3.new(0.1, 0.1, 0.1) mistPart.Position = position mistPart.Anchored = true mistPart.Transparency = 1 mistPart.CanCollide = false mistPart.Parent = newPartsFolder local pe = Instance.new("ParticleEmitter") pe.Color = ColorSequence.new(Color3.fromRGB(80 , 0, 0)) pe.Size = NumberSequence.new({NumberSequenceKeypoint.new(0, 1.5), NumberSequenceKeypoint.new(1, 5.0)}) pe.Texture = "rbxassetid://243664672" pe.Transparency = NumberSequence.new({NumberSequenceKeypoint.new(0, 0.4), NumberSequenceKeypoint.new(1, 1)}) pe.Lifetime = NumberRange.new(0.3, 0.6) pe.Rate = 0 pe.Speed = NumberRange.new(3, 8) pe.SpreadAngle = Vector2.new(180, 180) pe.Parent = mistPart pe:Emit(10) Debris:AddItem(mistPart, 1) end ------------------------------------------------------------------------------- -- BUCLE PRINCIPAL DE FÍSICA Y DETECCIÓN PRECISA DE IMPACTO ------------------------------------------------------------------------------- RunService.Heartbeat:Connect(function(dt) for part, data in pairs(ActiveDrops) do if not part or not part.Parent then ActiveDrops[part] = nil continue end local currentPos = part.Position local displacement = currentPos - data.lastPos local dist = displacement.Magnitude if dist > 0.001 then local raycast = workspace:Raycast(data.lastPos, displacement, data.params) if raycast then data:Impact(raycast) ActiveDrops[part] = nil else data.lastPos = currentPos end end -- FAILSAFE: Si la gota se atora en el aire (velocidad 0 y sin impactar nada) if ActiveDrops[part] and part.AssemblyLinearVelocity.Magnitude < 0.1 then -- Chequeo final hacia abajo local downRay = workspace:Raycast(part.Position, Vector3.new(0, -1, 0), data.params) if downRay then data:Impact(downRay) else -- Si de verdad se quedó flotando por un bug de Roblox, la reciclamos en vez de dejarla ahí ReturnPart(part) end ActiveDrops[part] = nil end end end) ------------------------------------------------------------------------------- -- FUNCIÓN PRINCIPAL DE SANGRE ------------------------------------------------------------------------------- function bloodService.CreateBlood(anchorPart: BasePart, parent, rate, particles, hitDirection) if not anchorPart or not anchorPart:IsA("BasePart") or not particles then return end local excluded = {parent, newPartsFolder} for _, plyr in ipairs(Players:GetPlayers()) do if plyr.Character then table.insert(excluded, plyr.Character) end end local rayParams = RaycastParams.new() rayParams.FilterType = Enum.RaycastFilterType.Exclude rayParams.FilterDescendantsInstances = excluded rayParams.IgnoreWater = true rayParams.RespectCanCollide = true local rng = Random.new() createBloodMist(anchorPart.Position) for i = 1, particles do local baseDir = hitDirection or (-anchorPart.CFrame.LookVector + Vector3.new(0, 0.5, 0)) if baseDir.Magnitude < 0.01 then baseDir = Vector3.new(0, 1, 0) end local speed = rng:NextNumber(15, 40) local spread = Vector3.new(rng:NextNumber(-8, 8), rng:NextNumber(-2, 10), rng:NextNumber(-8, 8)) local velocity = (baseDir.Unit * speed) + spread local bloodDrop: Part = GetPart() bloodDrop.Parent = newPartsFolder local startPos = anchorPart.Position + Vector3.new(0, 0.3, 0) bloodDrop.CFrame = CFrame.new(startPos) bloodDrop.AssemblyLinearVelocity = velocity local randomTextureId = bloodDecals[rng:NextInteger(1, #bloodDecals)] local decal = bloodDrop:FindFirstChild("Splatter") if decal then decal.Texture = randomTextureId decal.Color3 = Color3.new(1, 1, 1) end local bgui = bloodDrop:FindFirstChild("FlyingImage") if bgui then bgui.Enabled = true local img = bgui:FindFirstChild("Img") if img then img.Image = randomTextureId img.ImageColor3 = Color3.new(1, 1, 1) end end ActiveDrops[bloodDrop] = { part = bloodDrop, lastPos = startPos, params = rayParams, Impact = function(self, raycast) local part = self.part local hitPart = raycast.Instance if not part or not part.Parent then return end if not hitPart or not hitPart:IsA("BasePart") then ReturnPart(part) return end -- FAILSAFE: Ignorar barreras completamente invisibles (Hitboxes, Triggers) if hitPart.Transparency >= 1 and hitPart.Name ~= "HumanoidRootPart" then ReturnPart(part) return end playSplatSound(raycast.Position) local sizeX = rng:NextNumber(5.0, 9.0) local sizeZ = rng:NextNumber(3.5, 7.0) -- YA NO SE ANCLA: Lo preparamos para ser soldado al objeto dinámico part.Anchored = false part.CanCollide = false part.Massless = true part.Transparency = 1 local bguiChild = part:FindFirstChild("FlyingImage") if bguiChild then bguiChild.Enabled = false end local decalChild = part:FindFirstChild("Splatter") if decalChild then decalChild.Transparency = 0 end part.CFrame = CFrame.lookAt(raycast.Position, raycast.Position + raycast.Normal) * CFrame.Angles(math.rad(-90), 0, 0) * CFrame.Angles(0, rng:NextNumber(0, math.pi * 2), 0) -- SISTEMA DE WELDING: Pega la sangre al objeto/NPC que se está moviendo local weld = Instance.new("WeldConstraint") weld.Name = "BloodWeld" weld.Part0 = part weld.Part1 = hitPart weld.Parent = part TS:Create(part, tweenInfoIn, { Size = Vector3.new(sizeX, 0.05, sizeZ) }):Play() if decalChild then TS:Create(decalChild, tweenSecado, { Color3 = Color3.fromRGB(150, 150, 150) }):Play() end -- (Se retiró el efecto de que la sangre resbale hacia abajo porque causaba inestabilidad con los Welds de los NPCs en movimiento) RegisterNewDecal(part) task.delay(bloodSettings.Duration, function() if not part or not part.Parent then return end if decalChild then local tween = TS:Create(decalChild, tweenInfoOut, {Transparency = 1}) tween:Play() tween.Completed:Wait() end ReturnPart(part) end) end } task.delay(3, function() if bloodDrop and ActiveDrops[bloodDrop] then ReturnPart(bloodDrop) end end) if rate and rate >= 0 then task.wait(rate) end end end ------------------------------------------------------------------------------- -- CHARCO PROGRESIVO EN CADÁVERES ------------------------------------------------------------------------------- local function createBodyPool(char: Model) local root = char.PrimaryPart or char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso") if not root then return end local rayParams = RaycastParams.new() rayParams.FilterType = Enum.RaycastFilterType.Exclude rayParams.FilterDescendantsInstances = {char, newPartsFolder} rayParams.RespectCanCollide = true local raycast = workspace:Raycast(root.Position, Vector3.new(0, -8, 0), rayParams) if not raycast then return end local hitPart = raycast.Instance if not hitPart or hitPart.Transparency >= 1 then return end local poolPart = Instance.new("Part") poolPart.Name = "BloodPool" poolPart.Size = Vector3.new(0.2, 0.05, 0.2) poolPart.Anchored = false -- Preparado para soldadura (útil si mueren en un elevador móvil) poolPart.CanCollide = false poolPart.Massless = true poolPart.Transparency = 1 poolPart.CFrame = CFrame.lookAt(raycast.Position, raycast.Position + raycast.Normal) * CFrame.Angles(math.rad(-90), 0, 0) poolPart.Parent = newPartsFolder -- Soldar el charco también por si el suelo resulta ser algo que se mueve local weld = Instance.new("WeldConstraint") weld.Part0 = poolPart weld.Part1 = hitPart weld.Parent = poolPart local decal = Instance.new("Decal") decal.Texture = bloodDecals[1] decal.Face = Enum.NormalId.Top decal.Color3 = Color3.new(1, 1, 1) decal.Parent = poolPart local targetSize = math.random(8, 15) TS:Create(poolPart, TweenInfo.new(3, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), { Size = Vector3.new(targetSize, 0.05, targetSize) }):Play() TS:Create(decal, tweenSecado, { Color3 = Color3.fromRGB(150, 150, 150) }):Play() RegisterNewDecal(poolPart) task.delay(bloodSettings.Duration, function() if poolPart and poolPart.Parent then local tw = TS:Create(decal, tweenInfoOut, {Transparency = 1}) tw:Play() tw.Completed:Wait() poolPart:Destroy() end end) end ------------------------------------------------------------------------------- -- REGISTRO DE ENTIDADES (JUGADORES Y NPCS) ------------------------------------------------------------------------------- function registerCharacter(character: Model) if CollectionService:HasTag(character, "BloodSystemRegistered") then return end CollectionService:AddTag(character, "BloodSystemRegistered") local hum: Humanoid = character:FindFirstChildOfClass("Humanoid") or character:WaitForChild("Humanoid", 3) if not hum then return end hum:SetAttribute("OldHealth", hum.Health) hum.HealthChanged:Connect(function(newHealth) local oldHealth = hum:GetAttribute("OldHealth") if oldHealth and oldHealth > newHealth then if LocalPlayer and LocalPlayer.Character == character then flashScreenDamage() end pcall(function() bloodService.CreateBlood(character.PrimaryPart or character:FindFirstChild("HumanoidRootPart") or character:FindFirstChild("Torso"), character, 0, 2) end) end hum:SetAttribute("OldHealth", newHealth) end) hum.Died:Connect(function() local soundPart = Instance.new("Part") soundPart.Position = character:GetPivot().Position soundPart.Anchored = true soundPart.Transparency = 1 soundPart.Parent = workspace local s = Instance.new("Sound") s.SoundId = deathSoundId s.Volume = 1 s.Parent = soundPart s:Play() Debris:AddItem(soundPart, 2) task.spawn(function() local head = character:FindFirstChild("Head") or character.PrimaryPart if head then for burst = 1, 3 do bloodService.CreateBlood(head, character, 0, 2) task.wait(0.12) end end end) task.delay(0.4, function() createBodyPool(character) end) end) end function registerPlayer(player: Player) if CollectionService:HasTag(player, "BloodPlayerReg") then return end CollectionService:AddTag(player, "BloodPlayerReg") if player.Character then registerCharacter(player.Character) end player.CharacterAdded:Connect(registerCharacter) end for _, currentPlayer in pairs(Players:GetPlayers()) do registerPlayer(currentPlayer) end Players.PlayerAdded:Connect(registerPlayer) workspace.DescendantAdded:Connect(function(descendant) if descendant:IsA("Humanoid") then local charModel = descendant.Parent if charModel and charModel:IsA("Model") then registerCharacter(charModel) end end end) for _, obj in ipairs(workspace:GetDescendants()) do if obj:IsA("Humanoid") and obj.Parent and obj.Parent:IsA("Model") then registerCharacter(obj.Parent) end end