Roblox · Движение

Бег на Shift с выносливостью

Держишь Shift — бежишь быстрее, жёлтая полоска внизу тает; кончилась — переходишь на шаг, отдохнул — снова можно бежать. Скорость меняет общий модуль, поэтому бег честно складывается с замедлением и энергетиком.

Структура

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

ReplicatedStorage
└─ MovementEvent (RemoteEvent) — клиент → сервер: «нажал / отпустил»
ServerScriptService
├─ WeaponEffects (ModuleScript) — общий модуль: Effects.Modify считает скорость и прыжок
└─ MovementServer (Script) — что делает каждое действие
StarterPlayer — EnableMouseLockOption выкл. (Shift занят бегом)
└─ StarterPlayerScripts
   └─ SprintLocal (LocalScript) — клавиша, выносливость, полоска

Шаги

  1. ReplicatedStorage → «+» → RemoteEvent, переименуй в MovementEvent — по нему клавиши сообщают серверу «нажал / отпустил».
  2. ServerScriptService → «+» → Script, переименуй в MovementServer, вставь текст MovementServer из «Скриптов». Если он уже есть в игре — оставь один.
  3. ServerScriptService → «+» → ModuleScript, переименуй в WeaponEffects, вставь текст WeaponEffects из «Скриптов». Модуль один на всю игру: если он уже есть — пропусти этот шаг.
  4. StarterPlayer → StarterPlayerScripts → «+» → LocalScript, переименуй в SprintLocal, вставь текст SprintLocal из «Скриптов».
  5. StarterPlayer → в Properties выключи EnableMouseLockOption: иначе Shift ещё и переключает камеру (Shift Lock).
  6. Как работает: клиент только сообщает «нажал Shift / отпустил», а скорость меняет сервер через модуль — так её видят все, и замедление от оружия не теряется.
  7. Скорость считает модуль: (норма 16 + прибавки) × множители ÷ делители. Бег — «+10», поэтому под замедлением ×0,5 будет (16 + 10) × 0,5 = 13, а не 26; кончилось замедление — снова 26, отпустил Shift — снова 16.
  8. Клавиша назначается через ContextActionService: на телефоне для неё сама появляется кнопка на экране.
  9. Норма скорости — StarterPlayer → CharacterWalkSpeed (16). Поменяешь там — модуль возьмёт новую норму.
  10. Насколько быстрее — в MovementServer: Sprint = {{"WalkSpeed", "add", 10}}; можно "mul", 1.6 — тогда ×1,6.

Скрипты

MovementServer

Где: ServerScriptService · Script
Зачем: Добавляет и убирает изменения скорости по сигналам клавиш.

-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- серверные скрипты
local SSS = game:GetService("ServerScriptService")
-- общий модуль: он считает скорость и прыжок
local Effects = require(SSS:WaitForChild("WeaponEffects"))
-- сигналы от клавиш игрока
local event = RS:WaitForChild("MovementEvent")

-- что меняет каждое действие: {характеристика, операция, число}
local ACTIONS = {
    -- бег: скорость +10 (16 → 26)
    Sprint = {{"WalkSpeed", "add", 10}},
    -- красться: скорость ÷ 2, прыжок вдвое ниже
    Sneak = {{"WalkSpeed", "div", 2}, {"JumpHeight", "mul", 0.5}},
-- конец таблицы
}

-- игрок нажал или отпустил клавишу
event.OnServerEvent:Connect(function(player, action, on)
    -- что делает это действие
    local list = ACTIONS[action]
    -- неизвестное действие — пропускаем
    if not list then return end
    -- Humanoid игрока
    local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
    -- нет персонажа или он мёртв
    if not humanoid or humanoid.Health <= 0 then return end
    -- все изменения действия
    for i, m in ipairs(list) do
        -- имя изменения: Sprint1, Sneak1, Sneak2
        local id = action .. i
        -- клавишу нажали
        if on then
            -- добавляем изменение — пока не отпустят
            Effects.Modify(humanoid, id, m[1], m[2], m[3])
        -- отпустили
        else
            -- убираем — скорость снова считается без него
            Effects.Remove(humanoid, id)
        -- конец выбора
        end
    -- конец перебора
    end
-- конец обработчика
end)

SprintLocal

Где: StarterPlayer → StarterPlayerScripts · LocalScript
Зачем: Shift — бег, пока есть выносливость; полоска внизу экрана.

-- клавиши и кнопки на телефоне
local CAS = game:GetService("ContextActionService")
-- события каждого кадра
local RunService = game:GetService("RunService")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- сигнал на сервер
local event = RS:WaitForChild("MovementEvent")
-- этот игрок
local player = game.Players.LocalPlayer
-- запас выносливости
local MAX = 100
-- тратится в секунду бега — хватает на 4 секунды
local DRAIN = 25
-- восстанавливается в секунду, пока не бежишь
local REGEN = 15
-- выносливость сейчас
local stamina = MAX
-- бежим?
local sprinting = false

-- полоска выносливости — создаём из скрипта
local gui = Instance.new("ScreenGui")
-- имя
gui.Name = "StaminaGui"
-- не пропадает после смерти
gui.ResetOnSpawn = false
-- подложка полоски
local back = Instance.new("Frame")
-- четверть ширины экрана
back.Size = UDim2.fromScale(0.25, 0.02)
-- внизу по центру
back.Position = UDim2.fromScale(0.375, 0.93)
-- тёмная
back.BackgroundColor3 = Color3.fromRGB(30, 30, 40)
-- в ScreenGui
back.Parent = gui
-- сама полоска
local bar = Instance.new("Frame")
-- полная
bar.Size = UDim2.fromScale(1, 1)
-- жёлтая
bar.BackgroundColor3 = Color3.fromRGB(255, 200, 40)
-- без рамки
bar.BorderSizePixel = 0
-- в подложку
bar.Parent = back
-- на экран
gui.Parent = player:WaitForChild("PlayerGui")

-- включает и выключает бег
local function SetSprint(on)
    -- уже так — сервер не дёргаем
    if sprinting == on then return end
    -- запоминаем
    sprinting = on
    -- сервер добавит или уберёт +10 к скорости
    event:FireServer("Sprint", on)
-- конец функции
end

-- Shift (на телефоне — кнопка «Бег»)
CAS:BindAction("Sprint", function(_, state)
    -- нажали, и силы есть
    if state == Enum.UserInputState.Begin and stamina > 10 then
        -- бежим
        SetSprint(true)
    -- отпустили
    elseif state == Enum.UserInputState.End then
        -- шагом
        SetSprint(false)
    -- конец выбора
    end
    -- клавишу могут использовать и другие скрипты
    return Enum.ContextActionResult.Pass
-- true — создать кнопку на телефоне
end, true, Enum.KeyCode.LeftShift)
-- надпись на кнопке
CAS:SetTitle("Sprint", "Бег")

-- каждый кадр; dt — секунд с прошлого кадра
RunService.Heartbeat:Connect(function(dt)
    -- Humanoid игрока
    local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
    -- игрок идёт?
    local moving = humanoid and humanoid.MoveDirection.Magnitude > 0
    -- бежим
    if sprinting and moving then
        -- тратим силы
        stamina = math.max(0, stamina - DRAIN * dt)
        -- кончились — шагом
        if stamina == 0 then SetSprint(false) end
    -- не бежим
    elseif not sprinting then
        -- отдыхаем
        stamina = math.min(MAX, stamina + REGEN * dt)
    -- конец выбора
    end
    -- длина полоски = доля сил
    bar.Size = UDim2.fromScale(stamina / MAX, 1)
-- конец обработчика
end)

-- новый персонаж после смерти
player.CharacterAdded:Connect(function()
    -- бег выключен: у нового персонажа изменений нет
    sprinting = false
    -- силы полные
    stamina = MAX
-- конец обработчика
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) и убедись:

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