Roblox · Дополнительное

Капкан

Плоская ловушка на земле. Наступил — щелчок, урон, несколько секунд не можешь ни идти, ни прыгать; капкан темнеет. Потом сам взводится снова. Работает и на игроках, и на NPC.

Структура

Синим — что нужно создать.

Workspace
└─ Trap (Model) — PrimaryPart = Base
   ├─ Base (Part) — пластина: плоская, Anchored, CanCollide выкл.
   │  └─ Snap (Sound) — звук защёлкивания
   ├─ JawLeft, JawRight (Part) — зубья по краям, Anchored, CanCollide и CanTouch выкл.
   ├─ Configuration (Configuration)
   │  ├─ Damage (NumberValue) — урон, 15
   │  ├─ HoldTime (NumberValue) — секунд держит, 3
   │  └─ RearmTime (NumberValue) — секунд до повторного взвода, 5
   └─ TrapServer (Script)
ServerScriptService
└─ WeaponEffects (ModuleScript) — общий модуль: останавливает цель

Шаги

  1. Workspace → «+» → Model, переименуй в Trap. Капкан — модель: в ней пластина и зубья, а скрипт работает со всеми деталями сразу.
  2. В Trap: «+» → Part, переименуй в Base — пластина, на которую наступают. В Properties: Size — 3, 0.4, 3; Anchored — вкл.; CanCollide — выкл. (на капкан наступают, а не спотыкаются о него). Цвет — под землю, чтобы его было трудно заметить.
  3. Зубья: в Trap «+» → Part, переименуй в JawLeft. Size — 3, 0.8, 0.3; Anchored — вкл.; CanCollide — выкл.; CanTouch — выкл. (касания ловит только Base); тёмно-серый цвет. Поставь на один край пластины. Ctrl+D — копия JawRight, на другой край. Можно добавить ещё детали — пружину, цепь: перекрасятся вместе со всем капканом.
  4. Выдели модель Trap → в Properties PrimaryPart → нажми на Base в Explorer. Так модель знает, какая деталь главная (за неё её двигают и поворачивают).
  5. Configuration — папка для настроек (как Folder, значок шестерёнки): наведи на Trap → «+» → Configuration. Внутри создай по списку: «+» → NumberValue (если в скобках другой тип — его) → переименуй → в Properties впиши Value:
    • Damage = 15 — урон за одно попадание
    • HoldTime = 3 — сколько секунд держит: нельзя идти и прыгать
    • RearmTime = 5 — через сколько секунд капкан снова взведён
  6. Звук: Toolbox → вкладка Audio → «Trap», «Snap» или «Clang» → перетащи в Base, переименуй в Snap. Звук внутри детали слышно из места, где стоит капкан.
  7. В Trap: «+» → Script, переименуй в TrapServer, вставь текст TrapServer из «Скриптов».
  8. ServerScriptService → «+» → ModuleScript, переименуй в WeaponEffects, вставь текст WeaponEffects из «Скриптов». Модуль один на всю игру: если он уже есть — пропусти этот шаг.
  9. Как модель работает со скриптом: касание ловит только Base, а перекрашивается весь капкан — скрипт в начале запоминает цвета всех деталей модели и потом перекрашивает их одной функцией Paint.
  10. Как работает: капкан не меняет WalkSpeed сам, а просит модуль «установить скорость 0 на HoldTime секунд» — поэтому после капкана возвращается правильная скорость, даже если персонаж бежал или был под эффектами.
  11. Наборы: медвежий — Damage 30, HoldTime 4; ледяной — Damage 0, HoldTime 2, светло-голубой цвет; паутина — Damage 0, HoldTime 6.

Скрипты

TrapServer

Где: Workspace → Trap · Script
Зачем: Ловит наступившего: урон и остановка через модуль, потом взводится.

-- капкан — модель: пластина Base и зубья
local trap = script.Parent
-- пластина, на которую наступают
local base = trap:WaitForChild("Base")
-- урон и время
local config = trap:WaitForChild("Configuration")
-- звук защёлкивания — в пластине, чтобы звучал из капкана
local snap = base:WaitForChild("Snap")
-- общий модуль эффектов (страница «Эффекты оружия»)
local Effects = require(game:GetService("ServerScriptService"):WaitForChild("WeaponEffects"))
-- исходные цвета всех деталей капкана
local colors = {}
-- всё внутри модели
for _, part in pairs(trap:GetDescendants()) do
    -- только детали
    if part:IsA("BasePart") then
        -- запоминаем цвет
        colors[part] = part.Color
    -- конец проверки
    end
-- конец перебора
end

-- перекрашивает весь капкан сразу
local function Paint(snapped)
    -- каждую деталь
    for part, color in pairs(colors) do
        -- захлопнут — тёмно-красный, иначе исходный
        part.Color = snapped and Color3.fromRGB(110, 30, 30) or color
    -- конец перебора
    end
-- конец функции
end

-- взведён?
local armed = true

-- на пластину что-то наступило
base.Touched:Connect(function(otherPart)
    -- уже захлопнут
    if not armed then return end
    -- модель, которой принадлежит деталь
    local model = otherPart:FindFirstAncestorOfClass("Model")
    -- живая?
    local humanoid = model and model:FindFirstChildOfClass("Humanoid")
    -- не живая или мёртвая — пропускаем
    if not humanoid or humanoid.Health <= 0 then return end
    -- захлопнулся
    armed = false
    -- щёлк
    snap:Play()
    -- урон
    humanoid:TakeDamage(config.Damage.Value)
    -- стоит HoldTime секунд
    Effects.Modify(humanoid, "Trap", "WalkSpeed", "set", 0, config.HoldTime.Value)
    -- и не прыгает
    Effects.Modify(humanoid, "TrapJump", "JumpHeight", "set", 0, config.HoldTime.Value)
    -- весь капкан тёмно-красный — захлопнут
    Paint(true)
    -- ждём RearmTime секунд
    task.wait(config.RearmTime.Value)
    -- исходные цвета
    Paint(false)
    -- снова взведён
    armed = true
-- конец обработчика
end)

WeaponEffects

Где: ServerScriptService · ModuleScript
Зачем: Общий модуль эффектов — один на всю игру: оружие, усиления и бег вызывают его. Уже есть в игре — второй не нужен.

-- удаление по таймеру
local Debris = game:GetService("Debris")
-- игроки — для ослепления
local Players = game:GetService("Players")
-- норма скорости и прыжка игрока
local StarterPlayer = game:GetService("StarterPlayer")
-- изменения каждой цели; исчезнувшие цели забываются сами
local mods = setmetatable({}, {__mode = "k"})
-- во сколько раз сильнее урон по цели с меткой
local MARK_BONUS = 1.5
-- таблица модуля
local Effects = {}

-- читает настройку; нет такой — значит 0 (эффект выключен)
local function Get(config, name)
    -- ищем контейнер
    local v = config:FindFirstChild(name)
    -- есть — его значение
    if v then return v.Value end
    -- нет — ноль
    return 0
-- конец функции
end

-- норма характеристики: к ней всё возвращается, когда эффекты кончились
local function Base(humanoid, stat)
    -- уже знаем норму этой цели?
    local saved = humanoid:GetAttribute("Base" .. stat)
    -- знаем — её и берём (можно задать вручную атрибутом)
    if saved then return saved end
    -- NPC: его собственное значение (у быстрого врага — своё)
    local value = humanoid[stat]
    -- это игрок?
    local player = Players:GetPlayerFromCharacter(humanoid.Parent)
    -- скорость игрока
    if player and stat == "WalkSpeed" then
        -- норма из StarterPlayer — по умолчанию 16
        value = StarterPlayer.CharacterWalkSpeed
    -- прыжок игрока
    elseif player and stat == "JumpHeight" then
        -- норма из StarterPlayer — по умолчанию 7.2
        value = StarterPlayer.CharacterJumpHeight
    -- конец выбора
    end
    -- запоминаем норму на цели
    humanoid:SetAttribute("Base" .. stat, value)
    -- норма
    return value
-- конец функции
end

-- пересчитывает характеристику: норма + все действующие изменения
local function Recalc(humanoid, stat)
    -- сумма прибавок, произведение множителей и делителей, «установить»
    local add, mul, div, set = 0, 1, 1, nil
    -- текущее время
    local now = os.clock()
    -- все изменения этой цели
    for _, m in pairs(mods[humanoid] or {}) do
        -- та же характеристика и ещё действует
        if m.stat == stat and (m.untilTime == nil or now < m.untilTime) then
            -- прибавить
            if m.op == "add" then add += m.value
            -- умножить
            elseif m.op == "mul" then mul *= m.value
            -- разделить
            elseif m.op == "div" then div *= m.value
            -- установить — важнее всего
            elseif m.op == "set" then set = m.value end
        -- конец проверки
        end
    -- конец перебора
    end
    -- формула: (норма + прибавки) × множители ÷ делители
    local value = (Base(humanoid, stat) + add) * mul / div
    -- «установить» перекрывает всё (заморозка = 0)
    if set then value = set end
    -- не меньше нуля
    value = math.max(value, 0)
    -- прыжок
    if stat == "JumpHeight" then
        -- высота прыжка
        humanoid.JumpHeight = value
        -- та же высота через силу прыжка — работает при любой настройке игры
        humanoid.JumpPower = math.sqrt(2 * workspace.Gravity * value)
    -- скорость или здоровье
    else
        -- записываем
        humanoid[stat] = value
    -- конец выбора
    end
-- конец функции
end

-- изменение: id — имя, stat — WalkSpeed / JumpHeight / MaxHealth, op — add / mul / div / set
function Effects.Modify(humanoid, id, stat, op, value, seconds)
    -- на ноль не делим
    if op == "div" and value == 0 then return end
    -- список изменений цели
    mods[humanoid] = mods[humanoid] or {}
    -- он
    local list = mods[humanoid]
    -- без seconds — действует, пока не уберут
    local untilTime = nil
    -- изменение на время
    if seconds and seconds > 0 then
        -- до какого момента
        untilTime = os.clock() + seconds
        -- такое же изменение уже есть?
        local old = list[id]
        -- и оно кончится позже
        if old and old.untilTime and old.untilTime > untilTime then
            -- не укорачиваем
            untilTime = old.untilTime
        -- конец проверки
        end
        -- время конца — для видимых эффектов
        humanoid:SetAttribute(id .. "Until", untilTime)
        -- когда время выйдет
        task.delay(seconds + 0.05, function()
            -- это изменение
            local m = list[id]
            -- не продлили — кончилось
            if m and m.untilTime and os.clock() >= m.untilTime then
                -- убираем
                list[id] = nil
            -- конец проверки
            end
            -- пересчитываем: без эффектов — снова норма
            Recalc(humanoid, stat)
        -- конец отложенной функции
        end)
    -- конец проверки
    end
    -- записываем изменение (то же имя — заменяет старое)
    list[id] = {stat = stat, op = op, value = value, untilTime = untilTime}
    -- пересчитываем сразу
    Recalc(humanoid, stat)
-- конец функции
end

-- убирает изменение по имени (например, отпустили Shift)
function Effects.Remove(humanoid, id)
    -- изменения цели
    local list = mods[humanoid]
    -- такого нет
    if not list or not list[id] then return end
    -- какую характеристику оно меняло
    local stat = list[id].stat
    -- убираем
    list[id] = nil
    -- пересчитываем
    Recalc(humanoid, stat)
-- конец функции
end

-- видимый эффект, пока действует attr; второй такой же не создаём
local function Show(humanoid, attr, name, make)
    -- уже показан — время уже продлили
    if humanoid.Parent:FindFirstChild(name, true) then return end
    -- создаём эффект и узнаём, куда его положить
    local visual, parent = make()
    -- имя — чтобы найти его в следующий раз
    visual.Name = name
    -- показываем
    visual.Parent = parent
    -- отдельный поток — следит за временем
    task.spawn(function()
        -- пока цель жива и время не вышло
        while humanoid.Parent and humanoid.Health > 0 and os.clock() < (humanoid:GetAttribute(attr) or 0) do
            -- ждём
            task.wait(0.1)
        -- конец цикла
        end
        -- убираем эффект
        visual:Destroy()
    -- конец потока
    end)
-- конец функции
end

-- урон каждую секунду: огонь, яд, кровотечение
local function Dot(humanoid, attr, name, seconds, damage, make, movingBonus)
    -- продлеваем
    humanoid:SetAttribute(attr, math.max(humanoid:GetAttribute(attr) or 0, os.clock() + seconds))
    -- уже действует — хватит продления
    if humanoid.Parent:FindFirstChild(name, true) then return end
    -- видимый эффект
    local visual, parent = make()
    -- имя
    visual.Name = name
    -- показываем
    visual.Parent = parent
    -- центр цели — по нему видно, бежит ли она
    local root = humanoid.Parent:FindFirstChild("HumanoidRootPart")
    -- отдельный поток
    task.spawn(function()
        -- пока цель жива и время не вышло
        while humanoid.Health > 0 and os.clock() < humanoid:GetAttribute(attr) do
            -- раз в секунду
            task.wait(1)
            -- цель бежит?
            local moving = root and root.AssemblyLinearVelocity.Magnitude > 2
            -- кровотечение у бегущей цели
            if movingBonus and moving then
                -- вдвое сильнее
                humanoid:TakeDamage(damage * 2)
            -- иначе
            else
                -- обычный урон
                humanoid:TakeDamage(damage)
            -- конец выбора
            end
        -- конец цикла
        end
        -- эффект закончился
        visual:Destroy()
    -- конец потока
    end)
-- конец функции
end

-- отбрасывает цель от стрелка или от центра взрыва
local function Knockback(root, attacker, origin, power)
    -- центр взрыва, если он есть
    local from = origin
    -- это не взрыв
    if not from then
        -- центр атакующего
        local attackerRoot = attacker and attacker:FindFirstChild("HumanoidRootPart")
        -- не знаем, откуда толкать
        if not attackerRoot then return end
        -- толкаем от атакующего
        from = attackerRoot.Position
    -- конец проверки
    end
    -- направление по земле
    local flat = (root.Position - from) * Vector3.new(1, 0, 1)
    -- стоят в одной точке — направления нет
    if flat.Magnitude < 0.1 then return end
    -- точка приложения
    local attachment = Instance.new("Attachment")
    -- в центр цели
    attachment.Parent = root
    -- толчок; работает и на игроках — их физику считает их компьютер
    local push = Instance.new("LinearVelocity")
    -- куда приложен
    push.Attachment0 = attachment
    -- без ограничения силы
    push.MaxForce = math.huge
    -- назад и немного вверх
    push.VectorVelocity = flat.Unit * power + Vector3.new(0, power * 0.4, 0)
    -- толкаем
    push.Parent = root
    -- 0,2 секунды
    Debris:AddItem(push, 0.2)
    -- убираем и точку
    Debris:AddItem(attachment, 0.2)
-- конец функции
end

-- молния между двумя точками
local function Beam(a, b)
    -- парт-луч
    local beam = Instance.new("Part")
    -- не падает
    beam.Anchored = true
    -- ни с чем не сталкивается
    beam.CanCollide = false
    -- не мешает поиску целей
    beam.CanQuery = false
    -- светится
    beam.Material = Enum.Material.Neon
    -- фиолетовый
    beam.Color = Color3.fromRGB(150, 120, 255)
    -- длина — от точки до точки
    beam.Size = Vector3.new(0.2, 0.2, (b - a).Magnitude)
    -- между точками
    beam.CFrame = CFrame.new(a, b) * CFrame.new(0, 0, -(b - a).Magnitude / 2)
    -- показываем
    beam.Parent = workspace
    -- вспышка 0,2 секунды
    Debris:AddItem(beam, 0.2)
-- конец функции
end

-- цепная молния: перескакивает на ближайших
local function Chain(attacker, first, config)
    -- сколько прыжков
    local count = Get(config, "ChainCount")
    -- 0 — выключена
    if count <= 0 then return end
    -- дальность прыжка
    local range = Get(config, "ChainRange")
    -- не задана — 12
    if range <= 0 then range = 12 end
    -- первую цель уже ударили
    local hit = {[first] = true}
    -- откуда прыгает
    local from = first.Parent:FindFirstChild("HumanoidRootPart")
    -- настройки поиска
    local params = OverlapParams.new()
    -- не замечать из списка
    params.FilterType = Enum.RaycastFilterType.Exclude
    -- атакующего не бьём
    params.FilterDescendantsInstances = {attacker}
    -- прыжок за прыжком
    for i = 1, count do
        -- прыгать неоткуда
        if not from then return end
        -- ближайшая новая цель
        local best, bestRoot, bestDist = nil, nil, range
        -- всё вокруг
        for _, part in pairs(workspace:GetPartBoundsInRadius(from.Position, range, params)) do
            -- модель детали
            local model = part:FindFirstAncestorOfClass("Model")
            -- живая?
            local target = model and model:FindFirstChildOfClass("Humanoid")
            -- её центр
            local targetRoot = model and model:FindFirstChild("HumanoidRootPart")
            -- живая и ещё не ударена
            if target and targetRoot and target.Health > 0 and not hit[target] then
                -- расстояние
                local dist = (targetRoot.Position - from.Position).Magnitude
                -- ближе найденной
                if dist < bestDist then
                    -- запоминаем
                    best, bestRoot, bestDist = target, targetRoot, dist
                -- конец проверки
                end
            -- конец проверки
            end
        -- конец перебора
        end
        -- рядом никого — молния гаснет
        if not best then return end
        -- запоминаем
        hit[best] = true
        -- рисуем молнию
        Beam(from.Position, bestRoot.Position)
        -- урон молнии
        best:TakeDamage(Get(config, "ChainDamage"))
        -- следующий прыжок — отсюда
        from = bestRoot
    -- следующий прыжок
    end
-- конец функции
end

-- белый экран у задетого игрока
local function Blind(humanoid, seconds)
    -- это игрок?
    local player = Players:GetPlayerFromCharacter(humanoid.Parent)
    -- NPC не ослепить — у него нет экрана
    if not player then return end
    -- продлеваем
    humanoid:SetAttribute("BlindUntil", math.max(humanoid:GetAttribute("BlindUntil") or 0, os.clock() + seconds))
    -- экран игрока
    local playerGui = player:FindFirstChild("PlayerGui")
    -- уже ослеплён — хватит продления
    if not playerGui or playerGui:FindFirstChild("BlindEffect") then return end
    -- слой поверх экрана
    local gui = Instance.new("ScreenGui")
    -- имя
    gui.Name = "BlindEffect"
    -- на весь экран, включая верхнюю панель
    gui.IgnoreGuiInset = true
    -- белая заливка
    local flash = Instance.new("Frame")
    -- во весь экран
    flash.Size = UDim2.fromScale(1, 1)
    -- белая
    flash.BackgroundColor3 = Color3.new(1, 1, 1)
    -- почти непрозрачная
    flash.BackgroundTransparency = 0.05
    -- в слой
    flash.Parent = gui
    -- на экран игрока
    gui.Parent = playerGui
    -- следим за временем
    task.spawn(function()
        -- пока не вышло
        while gui.Parent and os.clock() < humanoid:GetAttribute("BlindUntil") do
            -- ждём
            task.wait(0.1)
        -- конец цикла
        end
        -- снова видно
        gui:Destroy()
    -- конец потока
    end)
-- конец функции
end

-- попадание: урон и все эффекты из Configuration
function Effects.Hit(attacker, humanoid, config, origin)
    -- цель уже мертва
    if humanoid.Health <= 0 then return end
    -- модель цели
    local character = humanoid.Parent
    -- её центр
    local root = character:FindFirstChild("HumanoidRootPart")
    -- урон
    local damage = Get(config, "Damage")
    -- на цели метка
    if os.clock() < (humanoid:GetAttribute("MarkedUntil") or 0) then
        -- урон ×1,5
        damage = damage * MARK_BONUS
    -- конец проверки
    end
    -- наносим урон
    humanoid:TakeDamage(damage)

    -- замедление
    if Get(config, "SlowTime") > 0 then
        -- скорость × SlowFactor на SlowTime секунд
        Effects.Modify(humanoid, "Slow", "WalkSpeed", "mul", Get(config, "SlowFactor"), Get(config, "SlowTime"))
    -- конец
    end
    -- заморозка
    if Get(config, "FreezeTime") > 0 then
        -- стоит
        Effects.Modify(humanoid, "Freeze", "WalkSpeed", "set", 0, Get(config, "FreezeTime"))
        -- не прыгает
        Effects.Modify(humanoid, "FreezeJump", "JumpHeight", "set", 0, Get(config, "FreezeTime"))
        -- ледяная подсветка
        Show(humanoid, "FreezeUntil", "FreezeEffect", function()
            -- подсветка всей модели
            local ice = Instance.new("Highlight")
            -- голубой лёд
            ice.FillColor = Color3.fromRGB(150, 220, 255)
            -- плотный
            ice.FillTransparency = 0.3
            -- белый контур
            ice.OutlineColor = Color3.new(1, 1, 1)
            -- на модель цели
            return ice, character
        -- конец функции
        end)
    -- конец
    end
    -- огонь, яд или ничего
    local effect = config:FindFirstChild("Effect") and config.Effect.Value or "None"
    -- огонь или яд
    if root and (effect == "Fire" or effect == "Poison") and Get(config, "EffectTime") > 0 then
        -- урон каждую секунду
        Dot(humanoid, "EffectUntil", "WeaponEffect", Get(config, "EffectTime"), Get(config, "EffectDamage"), function()
            -- огонь
            if effect == "Fire" then
                -- пламя на цели
                return Instance.new("Fire"), root
            -- конец
            end
            -- яд — облако
            local smoke = Instance.new("Smoke")
            -- ядовито-зелёное
            smoke.Color = Color3.fromRGB(90, 220, 60)
            -- полупрозрачное
            smoke.Opacity = 0.5
            -- на цели
            return smoke, root
        -- бонуса за бег нет
        end, false)
    -- конец
    end
    -- кровотечение
    if root and Get(config, "BleedTime") > 0 then
        -- урон каждую секунду
        Dot(humanoid, "BleedUntil", "BleedEffect", Get(config, "BleedTime"), Get(config, "BleedDamage"), function()
            -- капли
            local drops = Instance.new("ParticleEmitter")
            -- тёмно-красные
            drops.Color = ColorSequence.new(Color3.fromRGB(170, 0, 0))
            -- мелкие
            drops.Size = NumberSequence.new(0.2)
            -- живут полсекунды
            drops.Lifetime = NumberRange.new(0.5)
            -- 15 капель в секунду
            drops.Rate = 15
            -- падают вниз
            drops.Acceleration = Vector3.new(0, -30, 0)
            -- из центра цели
            return drops, root
        -- у бегущей цели — вдвое сильнее
        end, true)
    -- конец
    end
    -- отбрасывание
    if root and Get(config, "Knockback") > 0 then
        -- толкаем
        Knockback(root, attacker, origin, Get(config, "Knockback"))
    -- конец
    end
    -- вампиризм: доля урона в лечение
    local steal = Get(config, "Lifesteal")
    -- Humanoid атакующего
    local attackerHumanoid = attacker and attacker:FindFirstChildOfClass("Humanoid")
    -- вампиризм включён, атакующий жив
    if steal > 0 and attackerHumanoid and attackerHumanoid.Health > 0 then
        -- лечим, но не выше максимума
        attackerHumanoid.Health = math.min(attackerHumanoid.MaxHealth, attackerHumanoid.Health + damage * steal)
    -- конец
    end
    -- цепная молния, если ChainCount > 0
    Chain(attacker, humanoid, config)
    -- ослепление
    if Get(config, "BlindTime") > 0 then
        -- белый экран у игрока
        Blind(humanoid, Get(config, "BlindTime"))
    -- конец
    end
    -- метка
    if Get(config, "MarkTime") > 0 then
        -- продлеваем
        humanoid:SetAttribute("MarkedUntil", math.max(humanoid:GetAttribute("MarkedUntil") or 0, os.clock() + Get(config, "MarkTime")))
        -- красный контур
        Show(humanoid, "MarkedUntil", "MarkEffect", function()
            -- подсветка
            local mark = Instance.new("Highlight")
            -- без заливки
            mark.FillTransparency = 1
            -- красный контур — видно сквозь стены
            mark.OutlineColor = Color3.fromRGB(255, 40, 40)
            -- на модель цели
            return mark, character
        -- конец функции
        end)
    -- конец
    end
-- конец функции
end

-- усиление: kind = "Speed" (скорость) или "Jump" (прыжок)
function Effects.Boost(humanoid, kind, factor, seconds)
    -- мёртвым не помогает
    if humanoid.Health <= 0 then return end
    -- скорость
    local stat = "WalkSpeed"
    -- или прыжок
    if kind == "Jump" then stat = "JumpHeight" end
    -- × factor на seconds секунд; повторный приём продлевает
    Effects.Modify(humanoid, "Boost" .. kind, stat, "mul", factor, seconds)
    -- искры, пока действует
    Show(humanoid, "Boost" .. kind .. "Until", "Boost" .. kind .. "Effect", function()
        -- искры
        local sparkles = Instance.new("Sparkles")
        -- прыжок
        if kind == "Jump" then
            -- голубые
            sparkles.SparkleColor = Color3.fromRGB(80, 200, 255)
        -- скорость
        else
            -- жёлтые
            sparkles.SparkleColor = Color3.fromRGB(255, 220, 60)
        -- конец выбора
        end
        -- на центр персонажа
        return sparkles, humanoid.Parent:FindFirstChild("HumanoidRootPart")
    -- конец функции
    end)
-- конец функции
end

-- лечение: сразу amount, потом regen в секунду seconds секунд
function Effects.Heal(humanoid, amount, regen, seconds)
    -- мёртвых не лечим
    if humanoid.Health <= 0 then return end
    -- сразу, не выше максимума
    humanoid.Health = math.min(humanoid.MaxHealth, humanoid.Health + amount)
    -- восстановление выключено
    if regen <= 0 or seconds <= 0 then return end
    -- сколько в секунду
    humanoid:SetAttribute("RegenRate", regen)
    -- до какого времени
    humanoid:SetAttribute("RegenUntil", math.max(humanoid:GetAttribute("RegenUntil") or 0, os.clock() + seconds))
    -- уже восстанавливается — хватит продления
    if humanoid.Parent:FindFirstChild("RegenEffect", true) then return end
    -- зелёные искры, пока лечит
    Show(humanoid, "RegenUntil", "RegenEffect", function()
        -- искры
        local sparkles = Instance.new("Sparkles")
        -- зелёные
        sparkles.SparkleColor = Color3.fromRGB(80, 255, 120)
        -- на центр персонажа
        return sparkles, humanoid.Parent:FindFirstChild("HumanoidRootPart")
    -- конец функции
    end)
    -- отдельный поток
    task.spawn(function()
        -- пока жив и время не вышло
        while humanoid.Health > 0 and os.clock() < humanoid:GetAttribute("RegenUntil") do
            -- раз в секунду
            task.wait(1)
            -- прибавляем здоровье
            humanoid.Health = math.min(humanoid.MaxHealth, humanoid.Health + humanoid:GetAttribute("RegenRate"))
        -- конец цикла
        end
    -- конец потока
    end)
-- конец функции
end

-- отдаём модуль
return Effects

Проверка

Запусти Play (или Run) и убедись:

Собрать капкан одной командой