Roblox · Оружие

Нож: удар с ядом, огнём или замедлением

Ближний бой: клик — взмах, всех противников прямо перед персонажем задевает один раз. Откат между ударами. Урон, дальность и эффект (по умолчанию — отравленное лезвие) — в Configuration.

Структура

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

StarterPack — выдаётся каждому игроку
└─ Knife (Tool)
   ├─ Handle (Part) — модель; Anchored и CanCollide выкл.
   │  └─ Swing (Sound) — взмах
   ├─ Configuration (Configuration)
   │  ├─ Damage (NumberValue) — урон, 35
   │  ├─ Cooldown (NumberValue) — секунд между ударами, 0.6
   │  ├─ Range (NumberValue) — дальность, 6
   │  ├─ SlowTime (NumberValue) — 0 — не замедляет
   │  ├─ SlowFactor (NumberValue) — 0.5
   │  ├─ Effect (StringValue) — Poison
   │  ├─ EffectDamage (NumberValue) — 3
   │  ├─ EffectTime (NumberValue) — 5
   │  ├─ FreezeTime (NumberValue) — сейчас 0
   │  ├─ BleedTime (NumberValue) — сейчас 6
   │  ├─ BleedDamage (NumberValue) — сейчас 2
   │  ├─ Knockback (NumberValue) — сейчас 0
   │  ├─ Lifesteal (NumberValue) — сейчас 0.3
   │  ├─ ChainCount (NumberValue) — сейчас 0
   │  ├─ ChainDamage (NumberValue) — сейчас 10
   │  ├─ ChainRange (NumberValue) — сейчас 12
   │  ├─ BlindTime (NumberValue) — сейчас 0
   │  └─ MarkTime (NumberValue) — сейчас 0
   ├─ Animation (Animation) — AnimationId удара (можно оставить пустым)
   └─ KnifeServer (Script)
ServerScriptService
└─ WeaponEffects (ModuleScript) — общий модуль эффектов

Настройки эффектов

Значения в папке Configuration этого оружия. Меняй число в Properties — скрипт трогать не нужно.

Шаги

  1. Модель: Workspace → «+» → Part (или собери из нескольких партов и объедини: выдели их → вкладка Model → Union). Переименуй в Handle. В Properties: Anchored — выкл., CanCollide — выкл., Massless — вкл.
  2. Workspace → «+» → Tool, переименуй в Knife. Перетащи Handle внутрь Knife. Если в руке модель держится криво — плагин Tool Grip Editor (Toolbox → Plugins).
  3. Звук взмаха: Toolbox → вкладка Audio → «Swing» или «Slash» → перетащи в Handle, переименуй в Swing.
  4. Configuration — папка для настроек (как Folder, значок шестерёнки): наведи на Knife → «+» → Configuration. Внутри создай по списку: «+» → NumberValue (если в скобках другой тип — его) → переименуй → в Properties впиши Value:
    • Damage = 35 — урон за одно попадание
    • Cooldown = 0.6 — секунд между ударами
    • Range = 6 — дальность удара в studs
    • SlowTime = 0 — замедление: сколько секунд цель идёт медленнее; 0 — выключено
    • SlowFactor = 0.5 — замедление: во сколько раз медленнее — 0.5 значит вдвое
    • FreezeTime = 0 — заморозка: сколько секунд цель стоит и не может прыгать; 0 — выключено
    • Effect (StringValue) = Poison — огонь или яд: None — нет, Fire — пламя, Poison — зелёный дым (пиши точно так)
    • EffectDamage = 3 — огонь или яд: урон каждую секунду
    • EffectTime = 5 — огонь или яд: сколько секунд горит или действует яд
    • BleedDamage = 2 — кровотечение: урон в секунду (вдвое больше, если цель бежит)
    • BleedTime = 6 — кровотечение: сколько секунд идёт урон; 0 — выключено
    • Knockback = 0 — отбрасывание: сила толчка от тебя (у гранаты — от взрыва); 0 — выключено
    • Lifesteal = 0.3 — вампиризм: какая доля урона лечит тебя, 0.3 = 30 %; 0 — выключено
    • ChainCount = 0 — цепная молния: на сколько соседних целей перескочит; 0 — выключено
    • ChainDamage = 10 — цепная молния: урон каждой следующей цели
    • ChainRange = 12 — цепная молния: как далеко может перескочить
    • BlindTime = 0 — ослепление: сколько секунд у задетого игрока белый экран; 0 — выключено
    • MarkTime = 0 — метка: сколько секунд цель подсвечена, урон по ней ×1,5; 0 — выключено
  5. В Knife: «+» → Animation, имя Animation.
  6. Анимация удара (короткий взмах ~0,3 с) (по желанию): Avatar → Rig Builder → риг; Avatar → Animation Editor → поставь ключевые кадры; Priority — Action; Publish to Roblox → скопируй ID → вставь в AnimationId объекта Animation. Публикуй с того же аккаунта, что и игру. Без анимации всё работает.
  7. В Knife: «+» → Script «KnifeServer», вставь текст KnifeServer из «Скриптов».
  8. ServerScriptService → «+» → ModuleScript, переименуй в WeaponEffects, вставь текст WeaponEffects из «Скриптов». Модуль один на всю игру: если он уже есть — пропусти этот шаг.
  9. Перетащи Knife в StarterPack — будет у каждого игрока с начала игры. Или оставь в Workspace: подойди — и он попадёт в инвентарь.
  10. Как работает: Tool.Activated приходит и на сервер, поэтому нужен один серверный скрипт. Удар — поиск деталей в невидимой коробке перед персонажем (GetPartBoundsInBox): каждого, кто в ней, бьёт один раз через Effects.Hit.
  11. Effect пиши точно: None, Fire или Poison (с большой буквы).
  12. Наборы: ядовитый — Effect Poison; огненный — Effect Fire; тяжёлый — Damage 60, Cooldown 1.2, SlowTime 2; кинжал — Damage 15, Cooldown 0.3, Effect None; вампирский — Lifesteal 0.5.

Скрипты

KnifeServer

Где: StarterPack → Knife · Script
Зачем: Взмах: анимация и звук, через 0,15 с — удар по всем перед персонажем с уроном и эффектами.

-- нож (Tool)
local knife = script.Parent
-- модель ножа
local handle = knife:WaitForChild("Handle")
-- урон, откат, дальность, эффекты
local config = knife:WaitForChild("Configuration")
-- звук взмаха
local swingSound = handle:WaitForChild("Swing")
-- анимация удара
local anim = knife:WaitForChild("Animation")
-- время прошлого удара
local lastSwing = 0
-- загруженная анимация и чья она
local track, trackOwner = nil, nil

-- общий модуль эффектов (страница «Эффекты оружия»)
local Effects = require(game:GetService("ServerScriptService"):WaitForChild("WeaponEffects"))

-- клик (или тап), пока нож в руках; событие приходит и на сервер
knife.Activated:Connect(function()
    -- персонаж, который держит нож
    local character = knife.Parent
    -- его Humanoid
    local humanoid = character:FindFirstChildOfClass("Humanoid")
    -- центр персонажа
    local root = character:FindFirstChild("HumanoidRootPart")
    -- персонажа нет — выходим
    if not humanoid or not root then return end
    -- время этого удара
    local now = os.clock()
    -- откат ещё не прошёл
    if now - lastSwing < config.Cooldown.Value then return end
    -- запоминаем
    lastSwing = now
    -- анимация задана
    if anim.AnimationId ~= "" then
        -- загружаем один раз на персонажа
        if trackOwner ~= humanoid then
            -- загружаем
            track = humanoid:LoadAnimation(anim)
            -- запоминаем, чья
            trackOwner = humanoid
        -- конец проверки
        end
        -- взмах
        track:Play()
    -- конец проверки
    end
    -- звук взмаха
    swingSound:Play()
    -- момент удара посреди взмаха
    task.wait(0.15)
    -- настройки поиска
    local params = OverlapParams.new()
    -- режим «не замечать из списка»
    params.FilterType = Enum.RaycastFilterType.Exclude
    -- себя не задеваем
    params.FilterDescendantsInstances = {character}
    -- коробка удара — прямо перед персонажем
    local hitCFrame = root.CFrame * CFrame.new(0, 0, -config.Range.Value / 2 - 1)
    -- все детали в коробке 4 × 5 × Range
    local parts = workspace:GetPartBoundsInBox(hitCFrame, Vector3.new(4, 5, config.Range.Value), params)
    -- кого уже ударили — каждого по одному разу
    local hit = {}
    -- все детали в области
    for _, part in pairs(parts) do
        -- модель, которой принадлежит деталь
        local model = part:FindFirstAncestorOfClass("Model")
        -- есть ли у неё Humanoid
        local target = model and model:FindFirstChildOfClass("Humanoid")
        -- живой и ещё не ударен
        if target and target.Health > 0 and not hit[target] then
            -- запоминаем
            hit[target] = true
            -- урон и все эффекты из Configuration
            Effects.Hit(character, target, config)
        -- конец проверки
        end
    -- конец перебора
    end
-- конец обработчика
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) и убедись:

Configuration одной командой
  1. View → Command Bar — строка ввода внизу окна Studio.
  2. Скопируй скрипт кнопкой «Копировать», вставь в Command Bar, нажми Enter.
  3. В Workspace появится Configuration со всеми значениями Knife — имена и числа как на этой странице. Перетащи её внутрь Knife и поменяй числа под себя.
-- создаёт контейнер-значение
local function Value(class, valueName, value, parent)
    -- NumberValue или StringValue
    local v = Instance.new(class)
    -- имя — точно такое ждут скрипты
    v.Name = valueName
    -- значение
    v.Value = value
    -- куда положить
    v.Parent = parent
-- конец функции
end

-- папка настроек
local config = Instance.new("Configuration")
-- урон за удар
Value("NumberValue", "Damage", 35, config)
-- секунд между ударами
Value("NumberValue", "Cooldown", 0.6, config)
-- дальность удара в studs
Value("NumberValue", "Range", 6, config)
-- секунд замедления (0 — выкл.)
Value("NumberValue", "SlowTime", 0, config)
-- множитель скорости при замедлении
Value("NumberValue", "SlowFactor", 0.5, config)
-- секунд заморозки (0 — выкл.)
Value("NumberValue", "FreezeTime", 0, config)
-- None, Fire или Poison
Value("StringValue", "Effect", "Poison", config)
-- урон огня или яда в секунду
Value("NumberValue", "EffectDamage", 3, config)
-- секунд огня или яда
Value("NumberValue", "EffectTime", 5, config)
-- урон кровотечения в секунду (×2, если цель бежит)
Value("NumberValue", "BleedDamage", 2, config)
-- секунд кровотечения (0 — выкл.)
Value("NumberValue", "BleedTime", 6, config)
-- сила отбрасывания (0 — выкл.)
Value("NumberValue", "Knockback", 0, config)
-- доля урона в лечение атакующего: 0.3 = 30 %
Value("NumberValue", "Lifesteal", 0.3, config)
-- прыжков цепной молнии (0 — выкл.)
Value("NumberValue", "ChainCount", 0, config)
-- урон молнии за прыжок
Value("NumberValue", "ChainDamage", 10, config)
-- дальность прыжка молнии
Value("NumberValue", "ChainRange", 12, config)
-- секунд ослепления игрока (0 — выкл.)
Value("NumberValue", "BlindTime", 0, config)
-- секунд метки: урон по цели ×1,5 (0 — выкл.)
Value("NumberValue", "MarkTime", 0, config)
-- в Workspace — уже со всеми значениями
config.Parent = workspace
-- выделяем в Explorer
game:GetService("Selection"):Set({config})
-- сообщение в Output
print("Configuration готова: перетащи её внутрь Knife")
Собрать нож одной командой