Загрузка данных
local ServerStorage = game:GetService("ServerStorage")
local RunService = game:GetService("RunService")
local Debris = game:GetService("Debris")
-- Настройки спавна и карты
local MAP_RADIUS = 250 -- Радиус карты от центра (0, 0, 0) для рандомного спавна
local MIN_SPAWN_WAIT = 10 -- Минимальная пауза между торнадо (в секундах)
local MAX_SPAWN_WAIT = 25 -- Максимальная пауза между торнадо (в секундах)
-- Настройки категорий от 0 до 5
local CATEGORIES = {
[0] = { Radius = 25, MaxVolume = 15, PullForce = 35, Lift = 20, Speed = 14, Scale = 0.6, Duration = 25 },
[1] = { Radius = 40, MaxVolume = 50, PullForce = 50, Lift = 30, Speed = 18, Scale = 0.8, Duration = 30 },
[2] = { Radius = 60, MaxVolume = 150, PullForce = 70, Lift = 40, Speed = 22, Scale = 1.0, Duration = 35 },
[3] = { Radius = 85, MaxVolume = 400, PullForce = 95, Lift = 55, Speed = 26, Scale = 1.3, Duration = 40 },
[4] = { Radius = 115, MaxVolume = 1200, PullForce = 125, Lift = 70, Speed = 30, Scale = 1.7, Duration = 45 },
[5] = { Radius = 150, MaxVolume = 5000, PullForce = 160, Lift = 90, Speed = 35, Scale = 2.2, Duration = 55 },
}
-- Функция создания визуала торнадо (берёт шаблон из ServerStorage или создаёт свой)
local function createTornadoBody(scale)
local template = ServerStorage:FindFirstChild("TornadoTemplate")
local tornadoPart
if template and template:IsA("BasePart") then
tornadoPart = template:Clone()
tornadoPart.Size = template.Size * scale
else
tornadoPart = Instance.new("Part")
tornadoPart.Name = "TornadoCore"
tornadoPart.Size = Vector3.new(12 * scale, 120 * scale, 12 * scale)
tornadoPart.Transparency = 0.85
tornadoPart.Color = Color3.fromRGB(45, 45, 50)
tornadoPart.Material = Enum.Material.SmoothPlastic
-- Добавляем частицы вихря, если нет своего меша
local emitter = Instance.new("ParticleEmitter")
emitter.Color = ColorSequence.new(Color3.fromRGB(60, 60, 65))
emitter.Size = NumberSequence.new({
NumberSequenceKeypoint.new(0, 6 * scale),
NumberSequenceKeypoint.new(1, 35 * scale)
})
emitter.Transparency = NumberSequence.new(0.2, 0.8)
emitter.Lifetime = NumberRange.new(1.5, 2.5)
emitter.Rate = 80
emitter.Speed = NumberRange.new(30 * scale, 50 * scale)
emitter.RotSpeed = NumberRange.new(180, 360)
emitter.SpreadAngle = Vector2.new(15, 15)
emitter.Parent = tornadoPart
end
tornadoPart.Anchored = true
tornadoPart.CanCollide = false
tornadoPart.CanQuery = false
tornadoPart.Parent = workspace
return tornadoPart
end
-- Проверка: можно ли разрушать деталь
local function canDestroy(part, maxVolume)
if not part:IsA("BasePart") or part:IsA("Terrain") then return false end
if part.Name == "Baseplate" or part.Name == "TornadoCore" or part.Name == "TornadoTemplate" then return false end
-- Игнорируем слишком огромные платформы (например, пол карты)
if part.Size.X >= 200 or part.Size.Z >= 200 then return false end
-- Персонажей игроков затягиваем всегда
if part.Parent and part.Parent:FindFirstChildOfClass("Humanoid") then
return true
end
local volume = part.Size.X * part.Size.Y * part.Size.Z
return volume <= maxVolume
end
-- Функция запуска одного торнадо
local function spawnTornado()
local categoryLevel = math.random(0, 5)
local stats = CATEGORIES[categoryLevel]
print("Спавн торнадо! Категория: EF" .. categoryLevel)
local tornado = createTornadoBody(stats.Scale)
local halfHeight = tornado.Size.Y / 2
-- Рандомная точка старта
local currentX = math.random(-MAP_RADIUS, MAP_RADIUS)
local currentZ = math.random(-MAP_RADIUS, MAP_RADIUS)
-- Рандомный угол движения (в радианах)
local moveAngle = math.random() * math.pi * 2
local turnRate = 0
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude
overlapParams.FilterDescendantsInstances = {tornado}
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
rayParams.FilterDescendantsInstances = {tornado}
local elapsed = 0
local physicsTimer = 0
local connection
connection = RunService.Heartbeat:Connect(function(dt)
elapsed += dt
physicsTimer += dt
if elapsed >= stats.Duration then
connection:Disconnect()
tornado:Destroy()
print("Торнадо EF" .. categoryLevel .. " рассеялся.")
return
end
-- Плавная смена направления движения (блуждание)
if math.random() <= 0.05 then
turnRate = (math.random() - 0.5) * 1.5
end
moveAngle += turnRate * dt
currentX += math.cos(moveAngle) * stats.Speed * dt
currentZ += math.sin(moveAngle) * stats.Speed * dt
-- Определяем высоту земли с помощью луча сверху вниз
local rayOrigin = Vector3.new(currentX, 500, currentZ)
local rayResult = workspace:Raycast(rayOrigin, Vector3.new(0, -1000, 0), rayParams)
local groundY = rayResult and rayResult.Position.Y or 0
local tornadoCenter = Vector3.new(currentX, groundY + halfHeight, currentZ)
tornado.CFrame = CFrame.new(tornadoCenter) * CFrame.Angles(0, elapsed * 8, 0)
-- Разрушение и затягивание объектов каждые 0.08 сек
if physicsTimer >= 0.08 then
physicsTimer = 0
local basePos = Vector3.new(currentX, groundY, currentZ)
local partsInZone = workspace:GetPartBoundsInRadius(basePos, stats.Radius, overlapParams)
for _, part in ipairs(partsInZone) do
if canDestroy(part, stats.MaxVolume) then
-- Срываем с креплений, если объект был закреплён
if part.Anchored then
part.Anchored = false
part:BreakJoints()
-- Удаляем обломки через 25 секунд, чтобы игра не лагала
if not part.Parent:FindFirstChildOfClass("Humanoid") then
Debris:AddItem(part, 25)
end
end
-- Вычисляем вектор вращения вокруг оси торнадо и подъём вверх
local offset = part.Position - basePos
local flatOffset = Vector3.new(offset.X, 0, offset.Z)
local dist = math.max(flatOffset.Magnitude, 1)
local inwardDir = -flatOffset.Unit
local tangentDir = Vector3.new(-inwardDir.Z, 0, inwardDir.X) -- Вращение по кругу
local distFactor = math.clamp(1 - (dist / stats.Radius), 0.2, 1)
local velocity = (inwardDir * stats.PullForce * 0.6)
+ (tangentDir * stats.PullForce * 1.4)
+ Vector3.new(0, stats.Lift * distFactor, 0)
part.AssemblyLinearVelocity = velocity
end
end
end
end)
end
-- Бесконечный цикл рандомного появления торнадо
task.spawn(function()
while true do
local waitTime = math.random(MIN_SPAWN_WAIT, MAX_SPAWN_WAIT)
task.wait(waitTime)
spawnTornado()
end
end)