Загрузка данных


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)