local WALL = { open = 0, blocked = 0, moved = 0, noPath = 0, budgetHit = 0, fromSelect = 0, atFire = 0, pass = 0 } local searchCache = { model = nil, origin = nil, aim = nil, at = 0, candidate = nil, mode = nil } local function wallbangReach() return math.clamp(tonumber(CONFIG.wallbangMax) or 9, 1, 150) end local function worldClear(a, b) local delta = b - a if delta.Magnitude < 0.01 then return true end local params = Util.WorldParams(Util.Teammates()) return workspace:Raycast(a, delta, params) == nil end local function clearWith(targetParams, rayParams, origin, point) local toPoint = point - origin -- zero length cannot reach anything: a ray starting inside a part does not report it if toPoint.Magnitude < 0.01 then return false end local dir = toPoint.Unit local hit = workspace:Raycast(origin, dir * (toPoint.Magnitude + 2), targetParams) if not hit then return true end -- the first thing the shot would meet being their own weapon is not reaching them if hit.Instance:FindFirstAncestorOfClass("Tool") then return false end return workspace:Raycast(origin, dir * math.max(0.1, hit.Distance - 0.05), rayParams) == nil end findWallbangOrigin = function(origin, aimPoint, model, maxOffset) local now = os.clock() local memo = searchCache if memo.model == model and memo.candidate ~= nil and now - memo.at < (memo.candidate and 0.8 or 0.5) and memo.origin and (memo.origin - origin).Magnitude < 2 and memo.aim and (memo.aim - aimPoint).Magnitude < 3 then if memo.candidate == false then return nil end return memo.candidate[1], memo.candidate[2], memo.candidate[3] end local delta = aimPoint - origin if delta.Magnitude < 1 then return nil end local forward = delta.Unit local right = forward:Cross(Vector3.new(0, 1, 0)) right = right.Magnitude > 0.01 and right.Unit or Vector3.new(1, 0, 0) local up = Vector3.new(0, 1, 0) -- the clearance test hasLine runs, against raycast parameters built once for the whole -- search: building them per candidate cost more than the raycasts did local targetParams = RaycastParams.new() targetParams.FilterType = Enum.RaycastFilterType.Include targetParams.FilterDescendantsInstances = { model } local rayParams = Util.WorldParams(Util.Teammates()) local rayExclude = rayParams.FilterDescendantsInstances rayExclude[#rayExclude + 1] = model rayParams.FilterDescendantsInstances = rayExclude local airParams = Util.WorldParams(Util.Teammates()) local overlap = OverlapParams.new() overlap.FilterType = Enum.RaycastFilterType.Exclude local overlapExclude = airParams.FilterDescendantsInstances -- the target's own body must not read as solid ground for a point placed beside them overlapExclude[#overlapExclude + 1] = model overlap.FilterDescendantsInstances = overlapExclude overlap.MaxParts = 8 local AIR_RANGE = 40 -- One ray for the case that dominates (the line is blocked): the world between the point -- and the body. The target's model is excluded from these params, so a clear result means -- the line reaches them through open air - which also means the weapon check, the only part -- that needs the model, can wait until a candidate actually passes. local function reachable(candidate, point) local delta = point - candidate local length = delta.Magnitude if length < 0.01 then return false end local dir = delta.Unit if workspace:Raycast(candidate, dir * length, rayParams) ~= nil then return false end local hit = workspace:Raycast(candidate, dir * (length + 2), targetParams) if hit and hit.Instance:FindFirstAncestorOfClass("Tool") then return false end return true end -- What counts as reaching them. The aim point first, then the body itself, biggest part -- down: a clear line to an arm or a leg is a path even when the chest is buried, and -- testing three fixed points was a large share of the searches that came back empty. local points, seenPoint = {}, {} local function addPoint(p) if p and not seenPoint[p] then seenPoint[p] = true points[#points + 1] = p end end addPoint(aimPoint) local bodyParts = {} for _, d in ipairs(model:GetDescendants()) do if d:IsA("BasePart") and d.Transparency < 0.9 and not d:FindFirstAncestorOfClass("Tool") then bodyParts[#bodyParts + 1] = d end end table.sort(bodyParts, function(a, b) return a.Size.Magnitude > b.Size.Magnitude end) for i = 1, math.min(#bodyParts, 2) do addPoint(bodyParts[i].Position) end if #points == 0 then addPoint(aimPoint) end local budget = 900 local function attempt(candidate, radius, requireAir) if budget <= 0 then WALL.budgetHit = WALL.budgetHit + 1 return nil end budget = budget - 1 if requireAir and radius <= AIR_RANGE then if workspace:Raycast(origin, candidate - origin, airParams) ~= nil then WALL.rejAir = (WALL.rejAir or 0) + 1 return nil end elseif #workspace:GetPartBoundsInRadius(candidate, 0.55, overlap) > 0 then -- nothing solid may overlap the point, or the projectile starts inside it WALL.rejSolid = (WALL.rejSolid or 0) + 1 return nil end for i = 1, #points do local p = points[i] if reachable(candidate, p) then return candidate, radius, p end end WALL.rejLine = (WALL.rejLine or 0) + 1 return nil end -- Sampling: a cone around the muzzle, 16 azimuths at four elevations over the near radii -- and 8 azimuths at three further out. The old set was 12 fixed directions plus a thin -- ring, which at arm's length left gaps wider than a doorway - a clear window between two -- spokes was never tested, so the search reported no path with the gap in plain sight. local nearSteps = { 1.5, 2.5, 4.5, 7, 11, 14 } local farSteps = { 19, 27, 38, 52, 70, 92, 120 } local nearElev = { 0.12, 0.55, 1.0 } local farElev = { 0.35, 0.75 } local found, foundRadius, foundPoint, passUsed local function searchPath(requireAir, limit) local function ring(steps, elevations, azimuths) for _, radius in ipairs(steps) do if radius > limit then break end for _, sinE in ipairs(elevations) do local cosE = math.sqrt(1 - sinE * sinE) -- straight up is the same point at every azimuth local count = sinE >= 0.999 and 1 or azimuths for a = 0, count - 1 do local theta = (a / count) * math.pi * 2 local dir = (forward * math.cos(theta) + right * math.sin(theta)) * cosE + up * sinE local hit, moved, point = attempt(origin + dir.Unit * radius, radius, requireAir) if hit then return hit, moved, point end end end end return nil end local hit, moved, point = ring(nearSteps, nearElev, 18) if hit then return hit, moved, point end return ring(farSteps, farElev, 8) end for pass = 1, 2 do local requireAir = pass == 1 local limit = requireAir and maxOffset or math.min(maxOffset, AIR_RANGE) found, foundRadius, foundPoint = searchPath(requireAir, limit) if found then passUsed = pass break end end if found then WALL.pass = passUsed or 0 searchCache = { model = model, origin = origin, aim = aimPoint, at = now, candidate = { found, foundRadius, foundPoint } } return found, foundRadius, foundPoint end WALL.pass = 0 searchCache = { model = model, origin = origin, aim = aimPoint, at = now, candidate = false } return nil end