boom-bots/src/server/Modules/Utils/WeaponUtils.luau
2026-05-02 23:53:49 +02:00

156 lines
4.3 KiB
Plaintext

local WeaponUtils = {}
local rBotUtils = require(game.ReplicatedStorage.Shared.SharedUtils.sharedBotUtils)
local BotUtils = require("./BotUtils")
--////////////////////////////////////////////////////////
-- CONFIG
--////////////////////////////////////////////////////////
local VOXEL_SIZE = 0.5
local MAX_VOXELS_PER_EXPLOSION = 1500
local MATERIAL_RESISTANCE = {
[Enum.Material.Concrete] = 0.75,
[Enum.Material.Slate] = 0.65,
[Enum.Material.Wood] = 0.35,
[Enum.Material.Plastic] = 0.2,
Default = 0.5
}
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Include
local function getVoxelFolder()
return workspace:FindFirstChild("VoxelDebris")
end
local function snap(v)
return math.floor(v / VOXEL_SIZE + 0.5)
end
local DEBUG = true
local DEBUG_LIFETIME = 0.35
local function spawnDebugSphere(position: Vector3, radius: number, color: Color3)
local sphere = Instance.new("Part")
sphere.Shape = Enum.PartType.Ball
sphere.Anchored = true
sphere.CanCollide = false
sphere.Material = Enum.Material.Neon
sphere.Transparency = 0.75
sphere.Color = color
sphere.Size = Vector3.new(radius * 2, radius * 2, radius * 2)
sphere.Position = position
sphere.Parent = workspace
game:GetService("Debris"):AddItem(sphere, DEBUG_LIFETIME)
end
--////////////////////////////////////////////////////////
-- EXPLOSION (2 PHASE COLUMN SYSTEM)
--////////////////////////////////////////////////////////
function WeaponUtils.ExplosionQuery(origin: Vector3, innerRadius: number, outerRadius: number, maxDamage: number)
--------------------------------------------------------
-- BOT DAMAGE
--------------------------------------------------------
for _, model in pairs(workspace.Bots:GetChildren()) do
local bot = BotUtils.GetBotByModel(model)
if not bot then continue end
local position = rBotUtils.GetBotPosition(bot.key)
local damage = WeaponUtils.CalculateExplosionDamage(origin, position, outerRadius, maxDamage)
if damage > 0 then
bot.Components.Health:TakeDamage(damage)
end
end
if DEBUG then
spawnDebugSphere(origin, outerRadius, Color3.fromRGB(255, 60, 60)) -- red outer
spawnDebugSphere(origin, innerRadius, Color3.fromRGB(255, 140, 0)) -- orange inner
end
local folder = getVoxelFolder()
if not folder then return end
overlapParams.FilterDescendantsInstances = {folder}
local parts = workspace:GetPartBoundsInRadius(origin, innerRadius, overlapParams)
if #parts == 0 then return end
local radiusSq = innerRadius * innerRadius
--------------------------------------------------------
-- PHASE 1: FIND VALID VOXELS (SEEDS)
--------------------------------------------------------
local validColumns = {}
local processed = 0
for i = 1, #parts do
if processed > MAX_VOXELS_PER_EXPLOSION then break end
local part = parts[i]
if not part:IsA("BasePart") then continue end
local offset = part.Position - origin
local distSq = offset:Dot(offset)
if distSq > radiusSq then continue end
local dist = math.sqrt(distSq)
local falloff = 1 - (dist / innerRadius)
local power = falloff * falloff
local resistance = MATERIAL_RESISTANCE[part.Material] or MATERIAL_RESISTANCE.Default
if power > resistance then
-- STORE COLUMN KEY (X + Y ONLY)
local gx = snap(part.Position.X)
local gy = snap(part.Position.Y)
local key = gx .. "," .. gy
validColumns[key] = true
end
processed += 1
end
--------------------------------------------------------
-- PHASE 2: DESTROY FULL Z COLUMNS (NO CHECKS)
--------------------------------------------------------
for i = 1, #parts do
local part = parts[i]
if not part:IsA("BasePart") then continue end
local gx = snap(part.Position.X)
local gy = snap(part.Position.Y)
local key = gx .. "," .. gy
if validColumns[key] then
part:Destroy()
end
end
end
--////////////////////////////////////////////////////////
-- DAMAGE
--////////////////////////////////////////////////////////
function WeaponUtils.CalculateExplosionDamage(origin: Vector3, position: Vector3, radius: number, maxDamage: number)
local offset = position - origin
local distSq = offset:Dot(offset)
if distSq >= radius * radius then return 0 end
local dist = math.sqrt(distSq)
local normalized = dist / radius
local damage = maxDamage * (1 - normalized)^2
return math.clamp(damage, 0, maxDamage)
end
return WeaponUtils