local Players = game:GetService("Players") local RunService = game:GetService("RunService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local UserInputService = game:GetService("UserInputService") local TweenService = game:GetService("TweenService") local LP = Players.LocalPlayer local Camera = workspace.CurrentCamera ;(function() local old = getgenv().TribesOverlay if old and old.unload then pcall(old.unload) end local oldAim = getgenv().SilentAim if oldAim and oldAim ~= old and oldAim.unload then pcall(oldAim.unload) end end)(); local Shared = ReplicatedStorage:WaitForChild("Shared") local Combat = require(Shared.Combat) local Weapons = require(Shared.Weapons) local Simulation = require(Shared.Simulation) local Ballistics = require(Shared.Combat.Ballistics) local Client = LP.PlayerScripts.Combat local Util = require(Client.Util) local Effects = require(Client.Effects) local WeaponAction = ReplicatedStorage.Remotes.WeaponAction local CONFIG = { enabled = true, fov = 75, -- angular degrees, matches the on-screen ring targets = "both", -- "dummies" | "players" | "both" playerHitPart = "auto",-- "auto" | "head" | "body" dummyHitPart = "auto", -- "auto" | "head" | "body" teamCheck = true, -- skip same Tribe/Team gravityComp = true, -- lift the shot by the drop it takes over the flight viewDelay = 0, -- trim on the incoming-shot timing used by the dodge predict = false, -- LEAD: separate from everything else, off by default velocityWindow = 0.12, -- seconds of samples behind the filtered target velocity leadSeconds = 0, -- constant lead added on top of the replication delay hitGate = true, -- auto-fire only on a solve that simulated to a hit maxFlight = 0.35, -- do not take shots with a longer flight than this acquireDelay = 0.12, -- the lock must be held this long before firing leadScale = 1, -- 1.00 is the solved travel; below under-leads, above over-leads instantFire = true, -- drawn weapons: full charge with no draw wait autoFire = true, -- fire the instant the server will accept the shot killAura = false, -- swing at every enemy in melee range automatically killAuraRange = 0, -- 0 = the weapon's own range autoHeal = true, -- take a healing item by itself when hurt autoHealBelow = 70, -- heal at or under this much health (%) autoHealCooldown = 3, -- seconds between heal cycles simulate = true, -- wait for a clear line first (target visible) wallbang = true, -- when the world blocks the shot, move the origin until it does not wallbangMax = 9, -- search radius for a clear launch point, studs (0-150) desync = false, -- stepped position desync: jump the replication desyncAmount = 5, -- studs of displacement per jump desyncInterval = 0.18, -- seconds held between jumps desyncLocalSteady = 100, -- % of the displacement to cancel on our own camera realBody = false, -- mark the body the server resolves against, not the one we see realBodyHeat = true, -- colour the marker by how far off the body is realBodyScale = 1, -- size of the marker realBodyTag = true, -- distance label over the marker realBodyChevron = true, -- direction chevron around the crosshair desyncWalkOnly = false, -- only displace while actually moving desyncAimAware = true, -- step across the line an attacker is aiming along desyncVertical = false, -- give each step a height component as well desyncVerticalAmount = 2.5, -- studs of height per step desyncJitter = 0.25, -- vary the interval, so the cadence cannot be fitted dodgeDouble = false, -- step again inside the flight, after their lead is committed jitterDodge = false, -- shake side to side from their draw until their shot lands jitterMin = 3, -- shortest shake, studs jitterMax = 15, -- longest shake, studs jitterInterval = 0.01, -- seconds between shakes; 10ms means never still dodgeSmart = false, -- time the dodge off the attacker's real weapon and distance dodgeFacing = true, -- turn the body toward whoever we dodge dodgeSteadyCamera = true, -- hold the camera still through the step meleeHop = false, -- leave melee's vertical window when a swing winds up meleeHopHeight = 10, -- studs to lift; melee is refused beyond |Y| of 8 desyncReactive = true, -- step the moment an attack is about to land dodge = true, -- jump when someone fires or swings nearby, desync or not dodgeDistanceMin = 6, -- shortest lateral step, drawn at random per dodge dodgeDistanceMax = 10, -- longest lateral step dodgeHold = 0.4, -- seconds to stay displaced dodgeRamp = 0, -- seconds to ease in and out; 0 is an instant step like the desync dodgeRange = 55, -- only react to attackers within this range castStep = 1 / 120, mode = "direct", -- "direct" | "mortar" } local VIS = { enabled = true, wallbangIndicator = true, -- a dot and two words: is there a way past the cover, and did it fire box = true, boxStyle = "Corners", filledBox = false, fillAlpha = 0.82, healthBar = true, name = true, distance = true, weapon = true, headDot = true, skeleton = false, snapline = false, fovRing = true, watermark = true, realBodyColor = Color3.fromRGB(120, 220, 255), maxDistance = 600, textSize = 13, enemyColor = Color3.fromRGB(255, 65, 65), teamColor = Color3.fromRGB(85, 170, 255), dummyColor = Color3.fromRGB(255, 190, 60), -- chams, ported from the reference list: a Highlight pass and a part-material pass -- tracers, impact markers, hitsounds tracers = true, tracersEnemy = false, tracerColor = Color3.fromRGB(120, 200, 255), tracerColorEnemy = Color3.fromRGB(255, 120, 90), tracerLife = 0.22, tracerThickness = 0.35, -- 6.2 px per stud at 50 studs, so this reads as ~2 px markers = true, markerSize = 26, markerLife = 0.35, markerColorWorld = Color3.fromRGB(230, 230, 230), markerColorPlayer = Color3.fromRGB(255, 70, 70), hitsounds = true, hitsoundVolume = 60, chams = false, chamsFill = Color3.fromRGB(119, 120, 255), chamsFillAlpha = 80, chamsOutline = Color3.fromRGB(0, 0, 0), chamsOutlineDummy = Color3.fromRGB(255, 190, 60), chamsOutlineAlpha = 80, chamsThermal = true, chamsThrough = true, chamsRainbow = false, chamsFade = false, matChams = false, matChamsColor = Color3.fromRGB(0, 255, 255), matChamsAlpha = 0.25, matChamsMaterial = "ForceField", matChamsPattern = "Wave", matChamsSpeed = 3, matChamsRainbow = false, healthGood = Color3.fromRGB(60, 220, 90), healthBad = Color3.fromRGB(240, 60, 60), textColor = Color3.fromRGB(240, 242, 246), } local DEFAULTS = {} for k, v in pairs(CONFIG) do DEFAULTS["c_" .. k] = v end for k, v in pairs(VIS) do DEFAULTS["v_" .. k] = v end local STATE = { redirected = 0, lastShot = nil, lastSolution = nil, lastDenial = nil, lastHit = nil, pool = 0, aimActive = false, selectDebug = nil, lastError = nil, } -- Ring buffer of everything the attitude of the last few shots: what the client -- sent, what the server echoed back, and whether damage was confirmed. local SHOTLOG = {} local function logEvent(kind, text) SHOTLOG[#SHOTLOG + 1] = { kind = kind, t = os.clock(), text = text } if #SHOTLOG > 30 then table.remove(SHOTLOG, 1) end end STATE.log = SHOTLOG local closed = false local pending -- ------------------------------------------------------------------ target model local CollectionService = game:GetService("CollectionService") local function dummyFolder() local lobby = workspace:FindFirstChild("Lobby") local range = lobby and lobby:FindFirstChild("Range") return range and range:FindFirstChild("Dummies") end -- A dummy is a dummy by name and nothing else. Membership of the range folder says -- nothing (real players stand in there), and neither does the absence of a Player -- instance, which is what bots and anyone mid-spawn look like. Those two mistakes are -- what painted people with the dummy colour and steered shots at the dummy hitbox. -- Real usernames never contain the word, and the range targets are DUMMY / -- TrainingDummy, so one substring test separates them correctly. local function isDummy(model) if typeof(model) ~= "Instance" then return false end return string.find(string.upper(model.Name), "DUMMY", 1, true) ~= nil end -- Match combatants (bots / objective NPCs) are tagged, not Players. local function isCombatant(model) return CollectionService:HasTag(model, "Combatant") or CollectionService:HasTag(model, "Bot") end -- Game-authoritative team test: same MatchId and same character Team attribute. local function isTeammate(model) local ok, result = pcall(Util.IsTeammate, model) if ok and result ~= nil then return result == true end return false end -- The server resolves arrow damage against dedicated hitboxes, not the visible -- mesh: players carry an invisible TribesHull under the root, range dummies use -- Target parts. The visible Head sits above the hull, so aiming at it passes over -- the only part that can take damage. local function subPart(part, name) if part then local child = part:FindFirstChild(name) if child and child:IsA("BasePart") then return child end end return nil end -- Region selector: which part the arrow is steered at. "auto" uses the volume -- the game scores damage on (the chest hitbox), head/body steer explicitly. local function hitboxOf(model, root, head, player, dummy) local mode = "auto" if player then mode = CONFIG.playerHitPart elseif dummy then mode = CONFIG.dummyHitPart end local torso = model:FindFirstChild("UpperTorso") or model:FindFirstChild("Torso") if mode == "head" then return subPart(head, "Target") or head elseif mode == "body" then if root then local hull = root:FindFirstChild("TribesHull") if hull and hull:IsA("BasePart") then return hull end end return subPart(torso, "Target") or torso or subPart(root, "Target") or head end if root then local hull = root:FindFirstChild("TribesHull") if hull and hull:IsA("BasePart") then return hull end local target = root:FindFirstChild("Target") if target and target:IsA("BasePart") then return target end end -- range dummies carry their scoring plates as direct children of the model local reference = torso and torso.Position or head.Position local plate, plateDist for _, part in ipairs(model:GetChildren()) do if part:IsA("BasePart") and part.Name == "Target" then local d = (part.Position - reference).Magnitude if not plateDist or d < plateDist then plate, plateDist = part, d end end end if plate then return plate end local target = subPart(torso, "Target") if target then return target end target = subPart(head, "Target") if target then return target end return head end local targetVelocity local function validTarget(model) if not model or not model.Parent then return nil end local player = Players:GetPlayerFromCharacter(model) if player == LP then return nil end local dummy = isDummy(model) if not dummy and not (player or isCombatant(model)) then return nil end if CONFIG.teamCheck and isTeammate(model) then return nil end local humanoid = model:FindFirstChildOfClass("Humanoid") local head = model:FindFirstChild("Head") local root = model:FindFirstChild("HumanoidRootPart") or model.PrimaryPart if humanoid and humanoid.Health > 0 and head and head:IsA("BasePart") and root and root:IsA("BasePart") then return head, root, player, dummy, hitboxOf(model, root, head, player, dummy) end end local function rangeDummies() local out = {} local folder = dummyFolder() if folder then for _, model in ipairs(folder:GetChildren()) do if model:IsA("Model") and isDummy(model) then out[#out + 1] = model end end end return out end -- Util.Targets() is the game's own candidate list: other players plus every -- tagged Combatant / Bot. Using it keeps the aim aligned with what the server -- will actually accept damage against. local function combatTargets() local out = {} local ok, list = pcall(function() return Util.Targets() end) if ok and type(list) == "table" then for _, model in ipairs(list) do if model and model.Parent then out[#out + 1] = model end end end return out end local function targetPool() local out, seen = {}, {} local function add(model) if model and model.Parent and not seen[model] then seen[model] = true out[#out + 1] = model end end if CONFIG.targets == "dummies" then for _, model in ipairs(rangeDummies()) do add(model) end elseif CONFIG.targets == "players" then for _, model in ipairs(combatTargets()) do if not isDummy(model) then add(model) end end else for _, model in ipairs(combatTargets()) do add(model) end for _, model in ipairs(rangeDummies()) do add(model) end end return out end -- ------------------------------------------------------------------ aim -- Every weapon the game launches as a projectile: Bow, Crossbow, Musket, Pistol, -- Blunderbuss, Javelin, Knives, Sling, Bomb. Drawn weapons carry a charge in the -- payload, guns send a flat ShotSpeed instead. local function shotParams(tool) if not tool or not tool:IsA("Tool") then return nil end local weaponId = tool:GetAttribute("WeaponId") local definition = weaponId and Weapons.Get(weaponId) local tuning = definition and definition.tuning if not tuning then return nil end local kind = definition.kind or tool:GetAttribute("Kind") if not Combat.FiresProjectile(kind) then return nil end return tuning, kind, weaponId end local oneWayLatency local killAuraAim -- Where the shot is pointed: the volume the server resolves damage against, or the head -- when that is the configured hit part. local function aimPartFor(head, hitbox, player, dummy, root) local mode = player and CONFIG.playerHitPart or (dummy and CONFIG.dummyHitPart or "auto") -- Centre mass by default: the damage hitbox if the game has one, otherwise the torso/root, -- and the head only when there is nothing else to aim at. local part = hitbox or root or head if mode == "head" then part = head end if mode == "body" then part = hitbox or root or head end return part end -- The one thing the aim can wait on, and only when "wait for target visible" is on: a -- straight line from the muzzle to the aim point that the world does not block. local function hasLine(origin, point, model) local toPoint = point - origin if toPoint.Magnitude < 0.01 then return false end local dir = toPoint.Unit local targetParams = RaycastParams.new() targetParams.FilterType = Enum.RaycastFilterType.Include targetParams.FilterDescendantsInstances = { model } local hit = workspace:Raycast(origin, dir * (toPoint.Magnitude + 2), targetParams) if not hit then return true end local worldParams = Util.WorldParams(Util.Teammates()) local exclude = worldParams.FilterDescendantsInstances exclude[#exclude + 1] = model worldParams.FilterDescendantsInstances = exclude return workspace:Raycast(origin, dir * math.max(0.1, hit.Distance - 0.05), worldParams) == nil end -- Target selection: everyone inside the FOV circle, whoever sits closest to the -- crosshair. Being in the circle is the whole requirement - no range gate, no speed gate, -- no hold. The target already being shot at keeps a small bonus so the pick does not -- flick between two bodies standing together, and that bonus never beats a target that is -- clearly closer to the crosshair. -- Filled in with the rest of the origin work further down; selection needs it to decide -- whether a target that is behind cover is worth taking at all, and books its results here. local findWallbangOrigin local viewLag local WALL = { open = 0, blocked = 0, moved = 0, noPath = 0, budgetHit = 0, fromSelect = 0, atFire = 0, pass = 0 } STATE.wall = WALL local function wallbangReach() return math.clamp(tonumber(CONFIG.wallbangMax) or 9, 1, 150) end -- Target selection: everyone inside the FOV circle, whoever sits closest to the crosshair. -- Being in the circle is the whole requirement - no range gate, no speed gate, no hold. The -- target already being shot at keeps a small bonus so the pick does not flick between two -- bodies standing together, and that bonus never beats a target that is clearly closer to -- the crosshair. -- -- "Wait for target visible" used to be applied here as a flat filter, which is what stopped -- the wallbang doing its job: a target behind cover was discarded before the shot was ever -- planned, so there was nothing left to relocate, and the wallbang only fired on the shots -- where the current position happened to be visible while the lead point was not. Candidates -- are gathered in crosshair order and then walked: the first one with a clear line wins, and -- a blocked one is kept when the wallbang can find it a launch point. local function selectTarget(origin, prefer) local look = Util.GetAimRay().Direction.Unit local candidates = {} for _, model in ipairs(targetPool()) do local head, root, player, dummy, hitbox = validTarget(model) if head then local part = aimPartFor(head, hitbox, player, dummy, root) local delta = part.Position - origin local distance = delta.Magnitude if distance > 0.01 then local angle = math.deg(math.acos(math.clamp(look:Dot(delta.Unit), -1, 1))) if angle <= CONFIG.fov then local score = angle if prefer == model then score = score - 3 end candidates[#candidates + 1] = { model = model, head = head, hitbox = hitbox, part = part, angle = angle, distance = distance, root = root, score = score, velocity = (targetVelocity and targetVelocity(model, root)) or root.AssemblyLinearVelocity, direction = delta.Unit, } end end end end table.sort(candidates, function(a, b) return a.score < b.score end) -- only the few closest to the crosshair are worth a search; each one costs raycasts local searched, budget = 0, 3 for _, target in ipairs(candidates) do if hasLine(origin, target.part.Position, target.model) then target.blocked = false return target end target.blocked = true if CONFIG.wallbang and findWallbangOrigin and searched < budget then searched = searched + 1 local wallOrigin, _, wallPoint = findWallbangOrigin(origin, target.part.Position, target.model, wallbangReach()) if wallOrigin then target.wallOrigin = wallOrigin target.wallAim = wallPoint WALL.fromSelect = WALL.fromSelect + 1 return target end end if not CONFIG.simulate then return target end end if candidates[1] then STATE.selectDebug = { reason = "in FOV but behind cover, no launch point found" } end return nil end -- The direction that gets sent: straight at the aim point, lifted by the drop the -- projectile takes over the flight (off with "gravity compensation"). Nothing about the -- target's motion is predicted. local function aimDirection(origin, point, speed, gravity, arc) local distance = (point - origin).Magnitude if distance < 0.01 then return nil end local lift = 0 if CONFIG.gravityComp and (gravity or 0) > 0 and (speed or 0) > 0 then local flight = distance / speed lift = 0.5 * gravity * flight * flight * (arc == "high" and 1.8 or 1) end local delta = (point + Vector3.new(0, lift, 0)) - origin if delta.Magnitude < 0.01 then return nil end return delta.Unit end local function targetIsAirborne(model) local root = model and model:FindFirstChild("HumanoidRootPart") if not root then return false end return math.abs(root.AssemblyLinearVelocity.Y) > 8 end -- ------------------------------------------------------------------ prediction (ported) -- Ported from the earlier build, because it does two things this one did not: an exact -- intercept solved as a quartic in time, and a path check that simulates the actual -- trajectory and refuses the shot unless the round really does reach them. The velocity is -- filtered rather than read raw off the assembly, which is what makes the lead stable -- instead of alternating between too far and too short. local TRACK = {} -- The lead beyond the flight: how stale our view of the target is. The quartic below already -- accounts for their travel during the flight, so this is only the replication delay. local function leadTime() local lead = math.max(0, CONFIG.leadSeconds or 0) lead += oneWayLatency() lead += CONFIG.viewDelay or 0 lead += CONFIG.delayComp or 0 return math.clamp(lead, 0, 0.45) end local function valueAt(coefficients, x) local value = coefficients[#coefficients] for i = #coefficients - 1, 1, -1 do value = value * x + coefficients[i] end return value end local function rootsIn(coefficients, low, high) while #coefficients > 1 and math.abs(coefficients[#coefficients]) < 1e-12 do table.remove(coefficients) end local degree = #coefficients - 1 if degree == 0 then return {} end if degree == 1 then local root = -coefficients[1] / coefficients[2] return root >= low and root <= high and { root } or {} end local derivative = {} for i = 2, #coefficients do derivative[i - 1] = (i - 1) * coefficients[i] end local boundaries = rootsIn(derivative, low, high) table.insert(boundaries, 1, low) boundaries[#boundaries + 1] = high local roots = {} local function add(root) if #roots == 0 or math.abs(root - roots[#roots]) > 1e-7 then roots[#roots + 1] = root end end for i, a in ipairs(boundaries) do local fa = valueAt(coefficients, a) local scale = 0 for j, c in ipairs(coefficients) do scale += math.abs(c) * math.abs(a) ^ (j - 1) end if math.abs(fa) <= math.max(1e-9, scale * 1e-11) then add(a) end local b = boundaries[i + 1] if b then local fb = valueAt(coefficients, b) if (fa < 0 and fb > 0) or (fa > 0 and fb < 0) then local left, right = a, b for _ = 1, 48 do local middle = (left + right) * 0.5 local fm = valueAt(coefficients, middle) if (fa < 0 and fm <= 0) or (fa > 0 and fm >= 0) then left = middle else right = middle end end add((left + right) * 0.5) end end end return roots end local function filterUpdate(rec, z, now) if not rec.fp then rec.fp, rec.fv, rec.fpT = z, Vector3.new(0, 0, 0), now return end local dt = now - rec.fpT if dt <= 0 then return end if dt > 0.4 then -- respawn or teleport: restart cleanly rec.fp, rec.fv, rec.fpT = z, Vector3.new(0, 0, 0), now return end local predicted = rec.fp + rec.fv * dt local residual = z - predicted local err = residual.Magnitude -- smoothed so one bad frame cannot mark a target unstable rec.fResidual = (rec.fResidual or 0) * 0.9 + err * 0.1 -- Velocity is corrected as beta/dt, so beta must stay well under 1 at 60Hz or -- the estimate rings. A residual too large for one frame is a respawn, not -- motion, and resets the track instead of yanking the velocity. if err > 3 then rec.fp, rec.fv, rec.fpT = z, Vector3.new(0, 0, 0), now return end -- Humanoids accelerate hard and often. Low gains lag the truth, which -- overshoots decelerating targets and undershoots accelerating ones, so the -- gains open as soon as the residual shows real motion. local alpha, beta = 0.45, 0.14 if err > 0.6 then alpha, beta = 0.7, 0.30 end rec.fp = predicted + residual * alpha rec.fv = rec.fv + residual * (beta / dt) -- a humanoid cannot exceed this; anything more is a bad sample if rec.fv.Magnitude > 60 then rec.fv = rec.fv.Unit * 60 end rec.fpT = now end local function mediumGravity() local ok, g = pcall(function() return workspace.Gravity end) if ok and type(g) == "number" and g > 0 then return g end return 196.2 end local function solveIntercept(origin, point, velocity, speed, gravity, maxTime, arc) -- gravity here is the RELATIVE gravity and may be negative when a target is -- falling faster than the arrow drops; only absurd values are rejected. if speed <= 0 or speed ~= speed or gravity < -400 then return nil end local d = point - origin local coefficients = { d.X * d.X + d.Y * d.Y + d.Z * d.Z, 2 * (d.X * velocity.X + d.Y * velocity.Y + d.Z * velocity.Z), velocity.X ^ 2 + velocity.Y ^ 2 + velocity.Z ^ 2 + gravity * d.Y - speed ^ 2, gravity * velocity.Y, 0.25 * gravity ^ 2, } local limit = maxTime or Ballistics.MAX_LIFETIME local searchLimit = limit if arc == "high" then if gravity <= 0 then return nil end local leading = coefficients[5] local bound = 0 for i = 1, 4 do bound = math.max(bound, math.abs(coefficients[i] / leading)) end searchLimit = math.max(limit, 1 + bound) end local roots = rootsIn(coefficients, 0.0001, searchLimit) local startIndex, endIndex, increment = 1, #roots, 1 if arc == "high" then startIndex, endIndex, increment = #roots, #roots, 1 end for index = startIndex, endIndex, increment do local time = roots[index] if not time or time > limit then return nil end local displacement = d + velocity * time + Vector3.new(0, 0.5 * gravity * time * time, 0) local length = displacement.Magnitude if length > 0.001 and math.abs(length - speed * time) <= 0.001 then local direction = displacement.Unit local arrow = origin + direction * (speed * time) - Vector3.new(0, 0.5 * gravity * time * time, 0) local future = point + velocity * time local residual = (arrow - future).Magnitude local arrivalY = direction.Y * speed - gravity * time if residual <= 0.005 and (arc ~= "high" or arrivalY < 0) then return { direction = direction, time = time, future = future, residual = residual, arrivalY = arrivalY } end end end return nil end local function pathCheck(origin, solution, model, head, velocity, speed, gravity, lead, strictHead, targetGravity) local targetParams = RaycastParams.new() targetParams.FilterType = Enum.RaycastFilterType.Include targetParams.FilterDescendantsInstances = { model } local worldParams = Util.WorldParams(Util.Teammates()) local excluded = worldParams.FilterDescendantsInstances excluded[#excluded + 1] = model worldParams.FilterDescendantsInstances = excluded local shot = Ballistics.new(origin, solution.direction * speed, gravity, 0) local time = 0 local finish = math.min(solution.time + 0.05, Ballistics.MAX_LIFETIME) while time < finish do local dt = math.min(CONFIG.castStep, finish - time) local start, segment = Ballistics.Advance(shot, dt) local wall = workspace:Raycast(start, segment, worldParams) local advance = lead + time local drop = (targetGravity or 0) * 0.5 * advance * advance local relativeStart = start - (velocity * advance - Vector3.new(0, drop, 0)) local relativeSegment = segment - velocity * dt + Vector3.new(0, (targetGravity or 0) * 0.5 * ((advance + dt) ^ 2 - advance ^ 2), 0) local target = workspace:Raycast(relativeStart, relativeSegment, targetParams) if target then local fraction = relativeSegment.Magnitude > 0 and target.Distance / relativeSegment.Magnitude or 0 if wall and wall.Distance < segment.Magnitude * fraction then return false, "obstacle" end local hit = target.Instance local accepted if strictHead and head then accepted = hit == head or hit:IsDescendantOf(head) else accepted = hit == model or hit:IsDescendantOf(model) end if accepted then return true, hit.Name end return false, "hit " .. hit.Name end if wall then return false, "obstacle" end if shot.traveled > Ballistics.MAX_DISTANCE then return false, "range" end time += dt end return false, "no intersection" end local function aimPoints(model, head, hitbox, player, dummy) local points = {} local mode = player and CONFIG.playerHitPart or (dummy and CONFIG.dummyHitPart or "auto") if hitbox then points[#points + 1] = hitbox.Position end if mode ~= "head" then if not hitbox or hitbox.Position ~= head.Position then points[#points + 1] = head.Position end local torso = model:FindFirstChild("UpperTorso") or model:FindFirstChild("Torso") if torso and torso:IsA("BasePart") and torso.Position ~= head.Position then points[#points + 1] = torso.Position end end if #points == 0 then points[#points + 1] = head.Position end return points end local function shotClears(started) local held = started and (os.clock() - started) or 0 return held >= (CONFIG.acquireDelay or 0.3) end local function selectSolution(origin, speed, gravity, tuning, prefer) local look = Util.GetAimRay().Direction.Unit local candidates = {} for _, model in ipairs(targetPool()) do local head, root, player, dummy, hitbox = validTarget(model) if head then local delta = head.Position - origin local distance = delta.Magnitude if distance > 0.01 and distance <= ((tuning and tuning.range) or Ballistics.MAX_DISTANCE) then local angle = math.deg(math.acos(math.clamp(look:Dot(delta.Unit), -1, 1))) if angle <= CONFIG.fov then candidates[#candidates + 1] = { model = model, head = head, root = root, hitbox = hitbox, player = player, dummy = dummy, angle = angle } end end end end table.sort(candidates, function(a, b) return a.angle < b.angle end) -- keep the current lock: a target already held stays selected while it is -- still inside the cone, instead of swapping to whoever is nearest the cross if prefer then for index, candidate in ipairs(candidates) do if candidate.model == prefer then table.remove(candidates, index) table.insert(candidates, 1, candidate) break end end end local attempts, lastReason = 0, "no target in FOV" local solutions = {} for _, candidate in ipairs(candidates) do local velocity = targetVelocity(candidate.model, candidate.root) * (CONFIG.leadScale or 1) local lead = leadTime() local arc = CONFIG.mode == "mortar" and "high" or "low" -- a target in the air falls too: solve against the gravity difference local airborne = targetIsAirborne(candidate.model) local targetG = airborne and mediumGravity() or 0 local relGravity = gravity - targetG -- prediction off: aim at where the target is right now, no velocity, no lead if not CONFIG.predict then velocity = Vector3.new(0, 0, 0) lead = 0 targetG = 0 relGravity = gravity end for _, point in ipairs(aimPoints(candidate.model, candidate.head, candidate.hitbox, candidate.player, candidate.dummy)) do attempts += 1 local tries = {} tries[#tries + 1] = solveIntercept(origin, point + velocity * lead, velocity, speed, relGravity, nil, arc) for _, solution in ipairs(tries) do if solution then local clear, firstPart = pathCheck(origin, solution, candidate.model, candidate.hitbox or candidate.head, velocity, speed, gravity, lead, false, targetG) if clear then solution.model = candidate.model solution.head = candidate.head solution.hitbox = candidate.hitbox solution.aimPoint = point solution.velocity = velocity solution.firstPart = firstPart solution.angle = candidate.angle solution.attempts = attempts -- Cost is dominated by flight time because prediction error -- grows with it (measured: 0.42s flights miss by ~10 studs, -- 0.04s flights by ~0.5). Angle is a light tiebreak, and the -- locked target keeps a bonus so the selection stays sticky. solution.cost = (solution.time or 0) + (candidate.angle or 0) * 0.002 if prefer and candidate.model == prefer then solution.cost = solution.cost * 0.75 end solutions[#solutions + 1] = solution break end lastReason = "path " .. tostring(firstPart) else lastReason = "no trajectory" end end end end if #solutions > 0 then local best = solutions[1] for _, solution in ipairs(solutions) do if solution.cost < best.cost then best = solution end end best.options = #solutions return best end STATE.selectDebug = { candidates = #candidates, attempts = attempts, reason = lastReason } return nil end targetVelocity = function(model, root) -- the filter is the primary estimate; the window fit is only a fallback local rec = TRACK[model] if rec and rec.fp and rec.fpT and os.clock() - rec.fpT < 0.2 then return rec.fv end local record = TRACK[model] if record and record.velocity and os.clock() - record.at < 0.1 then return record.velocity end return root.AssemblyLinearVelocity end local function trackStability(model) local rec = TRACK[model] if not rec or not rec.fpT then return 1 end return rec.fResidual or 0 end local function filteredState(model, root) local rec = TRACK[model] if rec and rec.fp and rec.fpT then local dt = math.max(0, os.clock() - rec.fpT) if dt < 0.3 then return rec.fp + rec.fv * dt, rec.fv end end return root.Position, root.AssemblyLinearVelocity end local function velocityFit(samples, window) local latest = samples[#samples] if not latest then return nil end local count, st, stt, sx, sy, sz, stx, sty, stz = 0, 0, 0, 0, 0, 0, 0, 0, 0 local firstTime for _, sample in ipairs(samples) do local dt = sample.t - latest.t if dt >= -window then firstTime = firstTime or sample.t local x = sample.p.X - latest.p.X local y = sample.p.Y - latest.p.Y local z = sample.p.Z - latest.p.Z count += 1 st += dt; stt += dt * dt sx += x; sy += y; sz += z stx += dt * x; sty += dt * y; stz += dt * z end end if count < 2 or latest.t - firstTime < 0.02 then return nil end local denominator = count * stt - st * st if denominator <= 1e-12 then return nil end return Vector3.new((count * stx - st * sx) / denominator, (count * sty - st * sy) / denominator, (count * stz - st * sz) / denominator) end local function stateFit(samples, window) local newest = samples[#samples] if not newest then return nil end local n, st, st2, st3, st4 = 0, 0, 0, 0, 0 local sy, sty, st2y = 0, 0, 0 -- x local sy2, sty2, st2y2 = 0, 0, 0 -- y local sy3, sty3, st2y3 = 0, 0, 0 -- z for _, sample in ipairs(samples) do local dt = sample.t - newest.t if dt >= -window then local t2, t3, t4 = dt * dt, dt * dt * dt, dt * dt * dt * dt local x, y, z = sample.p.X - newest.p.X, sample.p.Y - newest.p.Y, sample.p.Z - newest.p.Z n += 1 st += dt; st2 += t2; st3 += t3; st4 += t4 sy += x; sty += dt * x; st2y += t2 * x sy2 += y; sty2 += dt * y; st2y2 += t2 * y sy3 += z; sty3 += dt * z; st2y3 += t2 * z end end if n < 6 then return nil end -- determinant of three column vectors local function det3(a, b, c) return a[1] * (b[2] * c[3] - b[3] * c[2]) - b[1] * (a[2] * c[3] - a[3] * c[2]) + c[1] * (a[2] * b[3] - a[3] * b[2]) end local col1 = { n, st, st2 } local col2 = { st, st2, st3 } local col3 = { st2, st3, st4 } local base = det3(col1, col2, col3) if math.abs(base) < 1e-9 then return nil end -- Cramer's rule: replace one column with the right-hand side vector local function solveAxis(rhs) local c0 = det3(rhs, col2, col3) / base local c1 = det3(col1, rhs, col3) / base local c2 = det3(col1, col2, rhs) / base return c0, c1, c2 end local c0x, c1x, c2x = solveAxis({ sy, sty, st2y }) local c0y, c1y, c2y = solveAxis({ sy2, sty2, st2y2 }) local c0z, c1z, c2z = solveAxis({ sy3, sty3, st2y3 }) local velocity = Vector3.new(c1x, c1y, c1z) local accel = Vector3.new(2 * c2x, 2 * c2y, 2 * c2z) if velocity.Magnitude > 250 or accel.Magnitude > 600 then return nil end -- RMS error of the curve against the samples: a low value means the target -- really is moving smoothly and the acceleration is trustworthy. local rms, count = 0, 0 for _, sample in ipairs(samples) do local dt = sample.t - newest.t if dt >= -window then local fitted = Vector3.new( newest.p.X + c0x + c1x * dt + c2x * dt * dt, newest.p.Y + c0y + c1y * dt + c2y * dt * dt, newest.p.Z + c0z + c1z * dt + c2z * dt * dt) rms += (fitted - sample.p).Magnitude ^ 2 count += 1 end end rms = count > 0 and math.sqrt(rms / count) or 99 return newest.p, velocity, accel, Vector3.new(c0x, c0y, c0z), rms end -- Filter every candidate's position each heartbeat, so the solve has a smoothed velocity to -- lead by rather than a raw assembly velocity that spikes on every animation step. RunService.Heartbeat:Connect(function() if closed then return end local now = os.clock() local probe = targetPool() STATE.trackPasses = (STATE.trackPasses or 0) + 1 STATE.trackPool = #probe for _, model in ipairs(probe) do local head, root = validTarget(model) if head then local record = TRACK[model] if not record or record.root ~= root then record = { root = root, samples = {} } TRACK[model] = record end local samples = record.samples local last = samples[#samples] if last and ((root.Position - last.p).Magnitude > 12 or now - last.t > 0.25) then table.clear(samples) end samples[#samples + 1] = { t = now, p = root.Position } while #samples > 1 and (now - samples[1].t > CONFIG.velocityWindow or #samples > 32) do table.remove(samples, 1) end filterUpdate(record, root.Position, now) record.velocity = velocityFit(samples, CONFIG.velocityWindow) local pos, vel, acc, offset, rms = stateFit(samples, math.max(CONFIG.velocityWindow, 0.22)) if pos and vel then record.origin, record.velocity2, record.accel, record.fitRms = pos, vel, acc, rms else record.velocity2, record.accel = nil, nil end record.at = now end end for model in pairs(TRACK) do if not validTarget(model) then TRACK[model] = nil end end end) -- The ported solve, applied to whatever the existing selection already picked (which keeps -- the wallbang, the lock and the "wait for visible" option working as they are). -- The trigger's gate. A solve only exists here if its trajectory was simulated to a hit, so -- the remaining questions are whether the flight is short enough to be worth taking, whether -- the target is juking (a lead will not hold), and whether the lock has been held long enough -- that they are not simply peeking past cover and about to step back behind it. local function shotVerified(solution) if not solution then return false end if not CONFIG.hitGate then return true end local function hold() local cal = STATE.cal if cal then cal.gated = (cal.gated or 0) + 1 end return false end local flight = solution.time if not flight then return hold() end if flight > (CONFIG.maxFlight or 0.35) then return hold() end if trackStability(solution.model) > 2.5 then return hold() end if not shotClears(STATE.lockSince) then return hold() end -- one more question the earlier build asked: is the shot actually aimed at the volume -- the server scores damage on if solution.firstPart and solution.model and not solution.firstPart:find("Hull") then -- hitting a limb is a hit, so this is a note rather than a refusal local cal = STATE.cal if cal then cal.limbHits = (cal.limbHits or 0) + 1 end end return true end local function solveForTarget(origin, target, speed, gravity) local velocity = (target.velocity or Vector3.new(0, 0, 0)) * (CONFIG.leadScale or 1) local lead = leadTime() local airborne = targetIsAirborne(target.model) local targetG = airborne and mediumGravity() or 0 local relGravity = gravity - targetG if not CONFIG.predict then velocity = Vector3.new(0, 0, 0) lead = 0 targetG = 0 relGravity = gravity end local arc = CONFIG.mode == "mortar" and "high" or "low" local best for _, point in ipairs(aimPoints(target.model, target.head, target.hitbox, nil, nil)) do local solution = solveIntercept(origin, point + velocity * lead, velocity, speed, relGravity, nil, arc) if solution then local clear, firstPart = pathCheck(origin, solution, target.model, target.hitbox or target.head, velocity, speed, gravity, lead, false, targetG) if clear then solution.model = target.model solution.head = target.head solution.hitbox = target.hitbox solution.aimPoint = point solution.velocity = velocity solution.firstPart = firstPart solution.lead = lead solution.targetGravity = targetG solution.angle = target.angle solution.distance = target.distance best = solution break end end end return best end -- ------------------------------------------------------------------ the shot solve -- The game's flight model, straight out of Shared.Combat.Ballistics.Advance: -- pos += vel*dt + g*(0.5*dt*dt); vel += g*dt; g = (0, -gravity, 0) -- which is a plain parabola with no drag, so the whole trajectory is closed form: -- p(t) = origin + dir*speed*t + (0, -0.5*gravity*t^2, 0) -- and hitting a moving target is picking the t that satisfies it. That is what this does: -- seed t from the straight-line distance, then iterate until the distance the round must -- cover equals speed*t. No lead knob and no lift knob are involved - the drop is part of the -- solve, so it cannot be compensated for twice. local function solveShot(origin, base, flatVel, leadShare, speed, gravity) speed = tonumber(speed) or 0 gravity = tonumber(gravity) or 0 if speed <= 1 then return nil end local gUp = Vector3.new(0, 0.5 * gravity, 0) local T = (base - origin).Magnitude / speed if T <= 0 or T > 5 then T = math.clamp(T, 0.02, 5) end local aim = base for _ = 1, 8 do aim = base + flatVel * (T * leadShare) local need = (aim - origin) + gUp * (T * T) local span = need.Magnitude if span < 0.001 or span > 1200 then return nil end local nextT = span / speed if math.abs(nextT - T) < 0.0005 then T = nextT break end T = nextT end aim = base + flatVel * (T * leadShare) local need = (aim - origin) + gUp * (T * T) if need.Magnitude < 0.001 then return nil end return need.Unit, T, aim end STATE.solveShot = solveShot -- The shot path every hook uses: who to point at, and the direction to send. local function planShot(origin, speed, gravity, prefer) local target = selectTarget(origin, prefer) if not target then STATE.selectDebug = { reason = "no target in FOV" } return nil end -- the launch point the wallbang found, if any: everything is solved from where the round -- will actually leave local solveOrigin = target.wallOrigin or origin -- The ported prediction: an exact intercept per body point (a quartic in time, so the -- target's own travel during the flight is inside the maths rather than bolted on), each -- one simulated end to end, and only accepted if the trajectory really reaches them. local solution = solveForTarget(solveOrigin, target, speed, gravity) if solution then solution.origin = target.wallOrigin solution.target = target solution.sentFrom = solveOrigin solution.angle = target.angle solution.distance = target.distance solution.latErr = 0 solution.originShift = (solveOrigin - origin).Magnitude STATE.lastSolution = solution STATE.lastLatErr = 0 STATE.lastSolve = { origin = solveOrigin, base = solution.aimPoint, aim = solution.future, flight = solution.time, speed = speed, gravity = gravity, lag = solution.lead or 0, lead = (solution.velocity or Vector3.new(0, 0, 0)) * (solution.lead or 0), } return solution end -- No verified trajectory: fall back to the plain compensated aim so the shot is still -- redirected instead of going wherever the crosshair points. It carries no flight time, -- which is what keeps the trigger from firing it. local point = target.wallAim or target.part.Position local direction if CONFIG.predict and (speed or 0) > 1 then local vel = target.velocity or Vector3.new(0, 0, 0) local lag = viewLag() local base = point + vel * lag local flatVel = Vector3.new(vel.X, 0, vel.Z) direction = solveShot(solveOrigin, base, flatVel, CONFIG.leadScale or 1, speed, gravity) end if not direction then direction = aimDirection(solveOrigin, point, speed, gravity, CONFIG.mode == "mortar" and "high" or nil) end if not direction then return nil end target.direction = direction target.aimPoint = point target.origin = target.wallOrigin target.firstPart = target.part.Name local sentFrom = target.origin or solveOrigin local direct = (point - sentFrom) local flatDir = Vector3.new(direction.X, 0, direction.Z) local flatPoint = Vector3.new(direct.X, 0, direct.Z) if flatDir.Magnitude > 0.001 and flatPoint.Magnitude > 0.001 then target.latErr = math.deg(math.acos(math.clamp(flatDir.Unit:Dot(flatPoint.Unit), -1, 1))) else target.latErr = 0 end target.originShift = (solveOrigin - origin).Magnitude STATE.lastLatErr = target.latErr STATE.lastSolution = target return target end -- ------------------------------------------------------------------ wallbang origin -- World-only clearance between two points: terrain, props and everything else the game -- makes a projectile answer to, with the teams we do not shoot at filtered out the same way -- the rest of the aim does. 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 -- Offsets are tried nearest first, inside a frame built on the shot itself. Up and out over -- what is in front is the move that clears a wall, a rock or a ridge; straight forward -- clears a corner the barrel is tucked behind; sideways clears what is beside us. A -- candidate also has to be reachable through open air, or the shot would start inside -- geometry and expire on its first step. -- The clearance test hasLine runs, against raycast parameters built once for the whole -- search. Building them per candidate (a RaycastParams plus a teammates table each time) cost -- more than the raycasts did: a full walk took 15 ms on a phone, which is a dropped frame. 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 -- Where a search left off, so selection can call this every frame without paying for a -- walk: the same target from the same place answers from here for half a second. A failed -- search is remembered for less time than a successful one. local searchCache = { model = nil, origin = nil, aim = nil, at = 0, candidate = nil, mode = nil } -- How far behind the server's copy of a target our view is: half the round trip plus the -- interpolation the game buffers with. Aiming a point-blank shot without this aims at where -- they were, which is the one thing that can still miss once the flight is nearly zero. viewLag = function() local lag = 0 pcall(function() lag = oneWayLatency() end) return math.clamp((lag or 0) + (CONFIG.viewDelay or 0) + 0.065, 0, 0.6) 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 -- Only ever runs when the shot as the game built it would have hit the wall in front: a -- clear line is left exactly as it is. The direction is re-solved from the new origin -- rather than reused, because the aim point is a different vector from up there, and the -- lead has to be re-applied with it. local function applyWallbang(origin, solution, speed, gravity) if not (CONFIG.wallbang and solution and solution.model and solution.model.Parent) then return end -- selection already moved this shot's origin; re-searching from the muzzle here would -- undo that pick and possibly land on a different point if solution.origin then return end local aimPoint = solution.aimPoint or (solution.part and solution.part.Position) if not aimPoint then return end if hasLine(origin, aimPoint, solution.model) then WALL.open = WALL.open + 1 WALL.last = "open" end WALL.blocked = WALL.blocked + 1 local reach = math.clamp(tonumber(CONFIG.wallbangMax) or 9, 1, 150) local candidate, moved, wallPoint = findWallbangOrigin(origin, aimPoint, solution.model, reach) if not candidate then WALL.noPath = WALL.noPath + 1 WALL.last = "blocked, no launch point" logEvent("wall", string.format("blocked, no clear origin within %.0f studs", reach)) return end local aimUse = wallPoint or aimPoint local close = (candidate - aimUse).Magnitude do local away = candidate - aimUse local along = away.Magnitude > 0.01 and away.Unit:Dot((aimUse - origin).Unit) or 0 WALL.spawnSide = along > 0.35 and "behind" or (along < -0.35 and "in front" or "side") end if close < 25 then -- Point-blank class: rebuild the aim, because the lead carried in here was solved for a -- flight from the muzzle. The base has to be the body point the path was actually -- checked against - re-solving onto a different one aims the round through whatever -- the search had just ruled out (their weapon, every time). local root = solution.model:FindFirstChild("HumanoidRootPart") local vel = root and root.AssemblyLinearVelocity or Vector3.new(0, 0, 0) local base = wallPoint or (solution.part and solution.part.Position) or aimUse local flight = (base - candidate).Magnitude / math.max(speed or 1, 1) aimUse = base + vel * (flight + viewLag()) WALL.spawnDist = close end local direction = aimDirection(candidate, aimUse, speed, gravity, CONFIG.mode == "mortar" and "high" or nil) if not direction then return end solution.origin = candidate solution.aimPoint = aimUse solution.direction = direction solution.wallband = moved WALL.moved = WALL.moved + 1 WALL.atFire = (WALL.atFire or 0) + 1 WALL.last = "moved " .. string.format("%.1f", moved) logEvent("wall", string.format("%s blocked -> origin %.1f studs over, line clear", solution.model.Name, moved)) end -- ------------------------------------------------------------------ can it land -- What the trigger asks before firing, and it is a different question from "is there a -- solution". A shot only lands if the line from the launch point to the body is clear of the -- world; the wallbang's relocated launch point is deliberately NOT consulted here, because a -- relocated shot is not a guaranteed hit and is not a reason to pull the trigger. The range -- cap is the round's own life: Combat.Ballistics.Expired stops it at 1200 studs or 5 seconds. local function canLand(origin) local target = selectTarget(origin, (lock and lock.model and lock.model.Parent) and lock.model or nil) if not (target and target.part) then return nil end local aim = target.part.Position if (aim - origin).Magnitude > 1150 then return nil end if not hasLine(origin, aim, target.model) then return nil end return target end STATE.canLand = canLand -- ------------------------------------------------------------------ calibration -- The server rebroadcasts every projectile it simulates (origin, velocity, the -- gravity it applied) and every impact position, keyed by "p:". -- A launch state plus its impact is a complete trajectory, and the horizontal -- equations are linear in time, so flight time and the applied gravity can both -- be solved exactly. Aggregating those gives the physics the server actually -- uses, not the physics the tuning table claims. local CAL = { gByKind = {}, gCount = {}, gSum = {}, leadByWeapon = {}, leadCount = {}, pending = {}, samples = 0, residuals = {}, lastG = nil, lastT = nil, dirMismatch = 0, originMismatch = 0, originChecked = 0, originDelta = nil, } STATE.cal = CAL STATE.median = function(list) if #list == 0 then return nil end local copy = table.clone(list) table.sort(copy) local mid = math.floor(#copy * 0.5) + 1 return copy[mid] end -- Solve flight time and gravity from origin + launch velocity + impact point. local function solveTrajectory(origin, velocity, impact) local t local vxz = math.sqrt(velocity.X * velocity.X + velocity.Z * velocity.Z) -- weak horizontal motion makes the flight time ill conditioned if vxz < 15 then return nil end -- least squares over the two linear (gravity-free) axes local tx, tz if math.abs(velocity.X) > 0.01 then tx = (impact.X - origin.X) / velocity.X end if math.abs(velocity.Z) > 0.01 then tz = (impact.Z - origin.Z) / velocity.Z end if tx and tz then if math.abs(tx - tz) > 0.05 * math.max(tx, tz) then return nil end t = (tx + tz) * 0.5 else t = tx or tz end if not t or t <= 0.02 or t > 2.5 then return nil end local g = 2 * (origin.Y + velocity.Y * t - impact.Y) / (t * t) if g < 0 or g > 160 then return nil end -- residual of the full parabola against the impact point local predicted = origin + velocity * t + Vector3.new(0, -0.5 * g * t * t, 0) return t, g, (predicted - impact).Magnitude end -- one round trip behind the server; half of it is the one-way view delay oneWayLatency = function() local ok, ping = pcall(function() return LP:GetNetworkPing() end) if ok and type(ping) == "number" and ping > 0 then return math.clamp(ping * 0.5, 0, 0.15) end return 0 end -- ------------------------------------------------------------------ target state -- Current target state from the tracking filter: position advanced to now and -- velocity. The root samples are filtered, so this is a smoothed estimate rather -- than the noisy instantaneous position. local function targetState(model, root) return root.Position, root.AssemblyLinearVelocity, nil end -- ------------------------------------------------------------------ ballistic constants -- Solve the server's ACTUAL launch speed from a single impact. We know the origin -- and unit direction we sent (O, u) and the server reports where the arrow hit (P). -- Along the horizontal axes gravity is irrelevant, so R = |P_xz - O_xz| / |u_xz| is -- the arc-length parameter, and the vertical axis then gives the speed in closed -- form: s^2 = 0.5 g R^2 / (O.y + u.y R - P.y). No fitting, no guessing. local function solveLaunchSpeed(origin, direction, impact, gravity) local u = direction local horizontalUnit = math.sqrt(u.X * u.X + u.Z * u.Z) if horizontalUnit < 1e-3 then return nil end local dx, dz = impact.X - origin.X, impact.Z - origin.Z local horizontal = math.sqrt(dx * dx + dz * dz) if horizontal < 1 then return nil end local R = horizontal / horizontalUnit local denominator = origin.Y + u.Y * R - impact.Y if denominator <= 0.05 then return nil end local speedSquared = 0.5 * gravity * R * R / denominator if speedSquared <= 0 then return nil end local speed = math.sqrt(speedSquared) if speed < 20 or speed > 2000 then return nil end return speed end -- ------------------------------------------------------------------ target lock -- Selection used to happen only on a shot, so the highlight appeared after -- firing. This resolves the best target continuously, keeps it while it stays in -- the cone, and hands it to both the highlight and the shot. local lock = { model = nil, solution = nil, at = 0 } STATE.lock = lock local LOCK_INTERVAL = 0.08 -- The muzzle, exactly where the game will launch from. The solve has to start at -- the same point the payload reports or the redirect is refused, so nothing here -- may move the origin: the anti-aim displaces the body, and this reads it back. local function aimOrigin(tool) return Util.ShotOrigin(tool, "Muzzle") end local function toolTuning() local char = LP.Character local tool = char and char:FindFirstChildWhichIsA("Tool") local tuning = tool and shotParams(tool) if not tuning then return nil end return tool, tuning end local function updateLock() if closed then return end if not CONFIG.enabled then lock.model, lock.solution = nil, nil return end -- with no prediction the lock needs no weapon: the muzzle when one is held, the -- camera otherwise, so the highlight and the target hud follow the circle at all times local char = LP.Character local tool = char and char:FindFirstChildWhichIsA("Tool") local origin if tool and shotParams(tool) then origin = aimOrigin(tool) else origin = Camera.CFrame.Position + Camera.CFrame.LookVector * 2 end local keep = lock.model and lock.model.Parent and lock.model or nil local solution = selectTarget(origin, keep) if solution then if lock.model ~= solution.model then lock.since = os.clock() end lock.model, lock.solution, lock.at = solution.model, solution, os.clock() STATE.lockSince = lock.since else lock.solution = nil -- Keep the acquisition clock across a brief flicker (a target stepping -- behind a post for a frame), but drop it if the gap is real. if not lock.at or os.clock() - lock.at > 0.5 then lock.model, lock.since = nil, nil STATE.lockSince = nil end end end task.spawn(function() while not closed do local ok, err = pcall(updateLock) if not ok then STATE.lastError = "lock: " .. tostring(err) end task.wait(LOCK_INTERVAL) end end) -- ------------------------------------------------------------------ tuning persistence -- The learned lead is the one thing that takes shots to rediscover, and a new -- match re-injects this script. Persist it to the executor's workspace so every -- match starts with the value the hit rate already converged on. local TUNE_FILE = "tribes_overlay_tuning.txt" local function loadTuning() if type(readfile) ~= "function" then return end local ok, data = pcall(readfile, TUNE_FILE) if not ok or type(data) ~= "string" then return end for key, value in data:gmatch("([%w_]+)=([%-%d%.]+)") do local number = tonumber(value) if number then if key == "leadTrim" then CONFIG.delayComp = number end if key == "viewDelay" then CONFIG.viewDelay = number end end end end local function saveTuning() if type(writefile) ~= "function" then return end pcall(writefile, TUNE_FILE, string.format("leadTrim=%.4f\nviewDelay=%.4f\n", CONFIG.delayComp or 0, CONFIG.viewDelay or 0)) end loadTuning() -- ------------------------------------------------------------------ aim hooks local originalProjectile = Effects.Projectile local projectileHook projectileHook = function(key, kind, origin, velocity, gravity, shooter, prediction) if not closed and CONFIG.enabled and shooter == LP.Character then local ok, err = pcall(function() local tool = shooter:FindFirstChildWhichIsA("Tool") local tuning = shotParams(tool) if not tuning then return end STATE.shotsSeen = (STATE.shotsSeen or 0) + 1 local speed = velocity.Magnitude -- this argument is the launch point the game just built and is about to -- put in the payload, so solving from it is what makes the redirect be -- accepted and the trajectory start where the server starts local stableOrigin = typeof(origin) == "Vector3" and origin or aimOrigin(tool) local kind = tuning.projectile or "Arrow" STATE.aimWeaponId = tool:GetAttribute("WeaponId") local locked = (lock.model and lock.model.Parent) and lock.model or nil local solution = planShot(stableOrigin, speed, gravity, locked) if solution then applyWallbang(stableOrigin, solution, speed, gravity) end if type(key) == "string" then local rec = CAL.pending[key] or {} rec.kind = kind rec.mine = true rec.weaponId = STATE.aimWeaponId rec.at = os.clock() -- recorded for every shot, not just solved ones: the tracer needs a -- launch point on any shot that lands, including ones with no target -- locked, and it used to be set only inside the branch below rec.fireOrigin = stableOrigin logEvent("shot", string.format("key=%s origin=%s", tostring(key), string.format("%.0f,%.0f,%.0f", stableOrigin.X, stableOrigin.Y, stableOrigin.Z))) if solution then rec.model = solution.model local tRoot = solution.model:FindFirstChild("HumanoidRootPart") rec.fireTargetPos = tRoot and tRoot.Position or nil local myRoot = LP.Character and LP.Character:FindFirstChild("HumanoidRootPart") local myHum = LP.Character and LP.Character:FindFirstChildOfClass("Humanoid") rec.mySpeed = myRoot and myRoot.AssemblyLinearVelocity.Magnitude or 0 rec.myMoving = myHum and myHum.MoveDirection.Magnitude > 0.1 or false rec.sentDir = solution.direction rec.sentOrigin = solution.origin or stableOrigin rec.assumedGravity = gravity end CAL.pending[key] = rec end pending = { at = os.clock(), tool = tool, origin = stableOrigin, rawOrigin = origin, speed = speed, solution = solution } STATE.lastSolution = solution if type(key) == "string" then STATE.myKeys = STATE.myKeys or {} STATE.myKeys[key] = os.clock() for k, t in pairs(STATE.myKeys) do if os.clock() - t > 8 then STATE.myKeys[k] = nil end end end if solution then velocity = solution.direction * speed local root = LP.Character and LP.Character:FindFirstChild("HumanoidRootPart") local vsRoot = root and math.deg(math.acos(math.clamp( solution.direction:Dot(root.CFrame.LookVector), -1, 1))) or -1 logEvent("aim", string.format( "-> %s angle=%.1f distance=%.0f speed=%.0f vsRoot=%.1f part=%s aimErr=%.2f\u{00B0}%s", solution.model.Name, solution.angle or 0, solution.distance or 0, speed, vsRoot, tostring(solution.firstPart), solution.latErr or 0, (solution.originShift or 0) > 0.01 and string.format(" origin moved %.1f studs", solution.originShift) or "")) else logEvent("aim", "no solution: " .. tostring(STATE.selectDebug and STATE.selectDebug.reason)) end end) if not ok then STATE.lastError = "aim: " .. tostring(err) pending = nil end end return originalProjectile(key, kind, origin, velocity, gravity, shooter, prediction) end Effects.Projectile = projectileHook -- Blunderbuss: the spread pattern is generated client-side from a unit direction, -- so redirect the unit vector and let the game's own spread maths follow it. local originalPellets = Effects.Pellets local pelletsHook pelletsHook = function(key, kind, origin, dir, speed, gravity, count, spread, seed, shooter, prediction) if not closed and CONFIG.enabled and shooter == LP.Character then local ok, err = pcall(function() local tool = shooter:FindFirstChildWhichIsA("Tool") local tuning = shotParams(tool) if not tuning then return end local solution = planShot(origin, speed, gravity) if solution then applyWallbang(origin, solution, speed, gravity) end pending = { at = os.clock(), tool = tool, origin = origin, rawOrigin = origin, speed = speed, solution = solution } STATE.lastSolution = solution if solution then dir = solution.direction logEvent("aim", string.format("-> %s (pellet spread) angle=%.1f distance=%.0f speed=%.0f", solution.model.Name, solution.angle or 0, solution.distance or 0, speed)) end end) if not ok then STATE.lastError = "pellets: " .. tostring(err) end end return originalPellets(key, kind, origin, dir, speed, gravity, count, spread, seed, shooter, prediction) end if type(originalPellets) == "function" then Effects.Pellets = pelletsHook end -- Keep this hook to plain table writes. Any :method() call inside a __namecall -- hook re-enters the metamethod on the same thread and Delta loses the pending -- namecall method, so the original FireServer dispatches under the wrong name. local originalNamecall local namecallHook = newcclosure(function(self, ...) if not closed and CONFIG.enabled and self == WeaponAction and getnamecallmethod() == "FireServer" then local action, payload = ... if action == "Shoot" and type(payload) == "table" and pending then local sol = pending.solution local sameOrigin = typeof(payload.origin) == "Vector3" and (payload.origin - pending.origin).Magnitude < 0.01 if sol and pending.tool == payload.tool and sameOrigin and os.clock() - pending.at < 0.25 then -- The launch point is written here, after the check that this payload is the -- one our own hook just built (that check compares the origin the game put in -- the payload against the one we solved from). The server launches a shot -- from the origin it is handed, so moving it here and re-solving the -- direction from the new point is the whole wallbang. if sol.origin and (sol.origin - payload.origin).Magnitude > 0.01 then payload.origin = sol.origin STATE.wbEngagedAt = os.clock() STATE.wbEngagedDist = (sol.origin - pending.origin).Magnitude end payload.dir = sol.direction STATE.redirected += 1 STATE.aimActive = true STATE.lastShot = { id = payload.id, charge = payload.charge, speed = pending.speed, target = sol.model, part = sol.firstPart, direction = payload.dir, origin = payload.origin, wallband = sol.wallband, mode = CONFIG.mode, angle = sol.angle, distance = sol.distance } else STATE.aimActive = false if sol then STATE.payloadMiss = (STATE.payloadMiss or 0) + 1 STATE.payloadMissReason = string.format( "toolMatch=%s originMatch=%s ageMs=%.0f", tostring(pending.tool == payload.tool), tostring(sameOrigin), (os.clock() - (pending.at or 0)) * 1000) end end end pending = nil end return originalNamecall(self, ...) end) originalNamecall = hookmetamethod(game, "__namecall", namecallHook) -- The game sends FX over two channels: CombatFX (reliable) and CombatFXU (unreliable), -- and both feed the same handler in the game's own code. High-frequency events - swings -- above all - arrive on the unreliable one, so listening to CombatFX alone silently -- missed every melee swing and probably a share of the shots as well. local function onCombatFXEvent(action, data) -- tally every action the server sends us: the handlers below are only as good as -- the events that actually arrive STATE.fxTally = STATE.fxTally or {} STATE.fxTally[tostring(action)] = (STATE.fxTally[tostring(action)] or 0) + 1 if type(data) ~= "table" then return end if action == "Charge" and CONFIG.dodge then local char = LP.Character local root = char and char:FindFirstChild("HumanoidRootPart") local attacker = data.attacker local theirChar = typeof(attacker) == "Instance" and ( attacker:IsA("Player") and attacker.Character or (attacker:IsA("Model") and attacker)) or nil if root and theirChar and theirChar ~= char and data.on then local them = theirChar:FindFirstChild("HumanoidRootPart") -- Draw (bow), Throw (thrown weapon), Heavy/Lunge (melee charge): all of -- them are windups, and all of them are broadcast before the attack lands. if CONFIG.jitterDodge and them and (them.Position - root.Position).Magnitude <= (CONFIG.dodgeRange or 55) then -- Jitter: a draw means a shot is coming, so the body shakes side to side for -- the whole draw-to-landing window instead of sitting still for any of it. -- There is nothing to time and nothing to predict, because no position lasts -- longer than the interval - there is no position to aim at. local axis local flat = Vector3.new(them.Position.X - root.Position.X, 0, them.Position.Z - root.Position.Z) if flat.Magnitude > 0.5 then axis = Vector3.new(-flat.Unit.Z, 0, flat.Unit.X) else local a = hvRandom:NextNumber(0, math.pi * 2) axis = Vector3.new(math.cos(a), 0, math.sin(a)) end -- measure the room once so the shake stays inside the map local params = Util.WorldParams(Util.Teammates()) local reach = math.max(CONFIG.jitterMin or 3, CONFIG.jitterMax or 15) local each = reach if workspace:Raycast(root.Position, axis * (reach + 1), params) then each = reach * 0.5 end STATE.jitterAcross = axis STATE.jitterReach = math.max(each, 1) if not STATE.jitterOffset then -- first draw of this window: start the shake fresh STATE.jitterSide = nil STATE.jitterNext = 0 STATE.jitterStarted = os.clock() STATE.jitters = (STATE.jitters or 0) + 1 end -- A window runs until the shot lands, and another draw inside it extends it -- rather than restarting it: they are still drawing, so the shot is still -- coming, and the body should not stop shaking in between. The timer here is -- only a backstop for a draw that never turns into anything. STATE.jitterUntil = math.max(STATE.jitterUntil or 0, os.clock() + 3) if not STATE.jitterLogged or os.clock() - STATE.jitterLogged > 1 then STATE.jitterLogged = os.clock() logEvent("jitter", string.format("%s drew: shaking %.0f-%.0f studs every %.0fms until their shot lands", tostring(data.weaponId or "weapon"), CONFIG.jitterMin or 3, math.min(CONFIG.jitterMax or 15, STATE.jitterReach or 15), (CONFIG.jitterInterval or 0.01) * 1000)) end end if them and (them.Position - root.Position).Magnitude <= (CONFIG.dodgeRange or 55) then local tuning = data.weaponId and Weapons.Get(data.weaponId) local windup = tuning and tuning.tuning and ( tuning.tuning.drawTime or tuning.tuning.windupTime) windup = tonumber(windup) or 0.35 -- the windup is only half of it: the shot still has to travel, and the -- weapon table says how fast local weapon = CONFIG.dodgeSmart and STATE.weaponFor(theirChar, nil, data.weaponId) or nil if CONFIG.dodgeSmart then -- Flick before the shot, and again once it is in the air. The flick on the -- draw puts the body somewhere other than where their aim is settling, so -- the shot is released against a position that is already wrong; the move -- in flight, timed off the shot's own arc, is the one they cannot answer, -- because a projectile's path is fixed the moment it leaves. Two positions -- per shot, and neither of them is where they were looking. -- -- The hold here only has to reach the release. Once the shot is out its -- arc says exactly how long the next displacement needs to last, and the -- hold is re-cut to that. -- The flick's length is the flight time. That is the whole rule: the body -- has to be somewhere else for as long as the shot is in the air, and it -- has to still be somewhere else when the shot lands - so the hold is the -- draw that is still to come plus the flight that follows it. Flight comes -- from the weapon's real launch speed and the distance to the shooter, -- both of which are right here. local lived = weapon and weapon.speed or 320 local flight = (them.Position - root.Position).Magnitude / math.max(lived, 1) STATE.dodgeFlight = flight local hold = math.min(windup + flight + (STATE.netRtt and STATE.netRtt() or 0) + 0.25, 2) STATE.dodgeSmartHold = hold STATE.dodgeForce = true STATE.armDodge(nil, them.Position) if not STATE.smartLogged or os.clock() - STATE.smartLogged > 1 then STATE.smartLogged = os.clock() logEvent("smart", string.format( "%s drawn (%.0fms windup, %.0f studs/s): flicking before the shot, held %.0fms to the release%s", tostring(weapon and weapon.id or "weapon"), windup * 1000, weapon and weapon.speed or 0, hold * 1000, weapon and weapon.cooldown > 0 and weapon.cooldown <= 0.5 and string.format(" (cadence %.0fms, re-flicks per shot)", weapon.cooldown * 1000) or "")) end else STATE.pendingDodge = { at = os.clock() + math.max(0.06, windup - 0.12), position = them.Position, } end -- Melee is validated inside a vertical window around the attacker's aim, -- and past eight studs of height difference the swing does not connect at -- all. A lift on the windup is therefore a miss by rule rather than by luck, -- and it beats a sidestep for the same reason: they are checking distance -- and arc, and the arc does not reach above them. Their own client builds -- that hit list from its delayed copy of our position, which is what makes -- reacting to the windup in time possible. Skipped while our own melee is -- in use, because that same window is what our swing needs. if CONFIG.meleeHop and not CONFIG.killAura then local kind = tuning and tuning.kind local melee = kind == "Sword" or kind == "Axe" or kind == "Spear" or kind == "Dagger" or kind == "Club" or kind == "Shield" if melee and (them.Position - root.Position).Magnitude <= 14 then local height = CONFIG.meleeHopHeight or 10 local params = Util.WorldParams(Util.Teammates()) -- take what headroom there is, but never less than the window needs if workspace:Raycast(root.Position, Vector3.new(0, height + 1, 0), params) then height = height * 0.5 end if height >= 8.5 then STATE.meleeOffset = Vector3.new(0, height, 0) STATE.meleeSince = os.clock() STATE.meleeUntil = os.clock() + math.max(0.4, windup + 0.3) STATE.hops = (STATE.hops or 0) + 1 logEvent("hop", string.format("%s windup %.0fms: lifting %.0f studs out of the melee window", tostring(kind), windup * 1000, height)) end end end end elseif data.on == false then -- the draw ended. If it was released, the projectile broadcast lands within a -- frame or two and re-times the hold to the real travel; if it was cancelled, -- nothing is coming and the displacement should stop being paid for. -- a scheduled move against a shot in the air is not cancelled by a draw ending if STATE.pendingDodge and not STATE.pendingDodge.smart then STATE.pendingDodge = nil end -- Deliberately NOT shortening the hold here. A charge dropping means the draw -- ended, and for a bow it ends because it was RELEASED - the arrow is out. Cutting -- the hold at that moment puts the body back on the base position in the window -- between the release and the projectile broadcast, which is where the shot lands. -- The hold was sized as draw plus flight, so it already covers everything that is -- coming; the release broadcast re-times it when it arrives. end end if action == "Denied" then STATE.lastDenial = { reason = data.reason, id = data.id, at = os.clock() } logEvent("denied", tostring(data.reason)) elseif action == "Projectile" and (data.shooter == LP or data.shooter == LP.Character) then -- The server rebroadcast of our own shot: compare with what we sent. This is the -- branch our own shots land in (the game's own handler drops its echo, this one does -- not), so both checks below belong here as well as on the incoming path: if the -- server echoes a different launch point than the one we handed it, the origin is -- being rebuilt server side and a wallbang can never land. local v = data.velocity if typeof(v) == "Vector3" then STATE.serverShot = { direction = v.Unit, speed = v.Magnitude, origin = data.origin, gravity = data.gravity, kind = data.kind, at = os.clock() } logEvent("server", string.format("speed=%.1f dir=%.3f,%.3f,%.3f", v.Magnitude, v.Unit.X, v.Unit.Y, v.Unit.Z)) -- what we asked it to launch from, against what it says it launched from: if -- these differ, the origin is being rebuilt server side and any shot whose -- direction was solved from a moved origin is aimed somewhere else entirely local sent = STATE.lastShot and STATE.lastShot.origin if sent and typeof(data.origin) == "Vector3" then local off = (sent - data.origin).Magnitude CAL.originDelta = off CAL.originChecked = (CAL.originChecked or 0) + 1 if off > 0.5 then CAL.originMismatch = (CAL.originMismatch or 0) + 1 end logEvent("origin", string.format("sent %.1f,%.1f,%.1f -> server %.1f,%.1f,%.1f (off %.2f)", sent.X, sent.Y, sent.Z, data.origin.X, data.origin.Y, data.origin.Z, off)) end local rec = type(data.key) == "string" and CAL.pending[data.key] or nil if rec then if rec.sentOrigin and typeof(data.origin) == "Vector3" then local off = (rec.sentOrigin - data.origin).Magnitude CAL.originDelta = off CAL.originChecked = (CAL.originChecked or 0) + 1 if off > 0.5 then CAL.originMismatch = (CAL.originMismatch or 0) + 1 end end if rec.sentDir then local angle = math.deg(math.acos(math.clamp(rec.sentDir:Dot(v.Unit), -1, 1))) CAL.dirAngle = angle if angle > 2 then CAL.dirMismatch = (CAL.dirMismatch or 0) + 1 end end end end elseif action == "Projectile" then -- An arrow we can see coming: work out when it will reach us and step the -- hitbox at that moment. Their aim was committed at release, so moving on -- arrival is what turns a correct shot into an incorrect one. if CONFIG.dodge and data.shooter ~= LP and data.shooter ~= LP.Character then local char = LP.Character local root = char and char:FindFirstChild("HumanoidRootPart") if root and typeof(data.origin) == "Vector3" and typeof(data.velocity) == "Vector3" then local shooterChar = typeof(data.shooter) == "Instance" and ( data.shooter:IsA("Player") and data.shooter.Character or (data.shooter:IsA("Model") and data.shooter)) or nil local shooterRoot = shooterChar and shooterChar:FindFirstChild("HumanoidRootPart") local near = shooterRoot and (shooterRoot.Position - root.Position).Magnitude or (data.origin - root.Position).Magnitude -- fire close to us and we move, regardless of where it was aimed: the -- point is to be somewhere else before it gets here if near > (CONFIG.dodgeRange or 55) then -- worth saying out loud: a shot ignored by the react range looks exactly -- like a dodge that failed if not STATE.rangeLogged or os.clock() - STATE.rangeLogged > 2 then STATE.rangeLogged = os.clock() logEvent("smart", string.format( "%s fired from %.0f studs, outside the %.0f-stud react range - ignored", tostring(data.kind), near, CONFIG.dodgeRange or 55)) end end if near <= (CONFIG.dodgeRange or 55) then -- the launch point, so the dodge has the shot's own line to work from local aimAt = data.origin -- The manual dodge always happens. Smart is only ever allowed to change -- how long the flick is held and which side it takes - it can never be the -- reason no flick happened. So it runs first, inside a guard, and the flick -- is taken afterwards regardless of how that went: a throw up there would -- otherwise kill the whole handler and silently disable dodging, which is -- precisely how smart ended up worse than the manual dodge it was meant to -- improve. local hit, impactIn, miss, aimedAtBase, flight, weapon, rapid, offText if CONFIG.dodgeSmart then local ok, err = pcall(function() local rtt = STATE.netRtt and STATE.netRtt() or 0 local oneWay = rtt * 0.5 local offset = STATE.appliedOffset or Vector3.new(0, 0, 0) local gravity = tonumber(data.gravity) or 0 local base = root.Position - offset flight = (root.Position - data.origin).Magnitude / math.max(data.velocity.Magnitude, 1) weapon = STATE.weaponFor(shooterChar, data.kind) rapid = weapon and weapon.cooldown > 0 and weapon.cooldown <= 0.5 or false -- where the shot is headed, from its own arc, against both places the -- body can be: the displaced one it holds now, and the base it -- returns to when the hold ends local arcHit, impactAt, arcMiss = STATE.shotImpact(data.origin, data.velocity, gravity, oneWay, root.Position, 2.4) local baseHit, baseImpact, baseMiss = STATE.shotImpact(data.origin, data.velocity, gravity, oneWay, base, 2.4) hit, impactIn, miss = arcHit, arcHit and (impactAt - oneWay) or nil, arcMiss if not arcHit and baseHit then hit, impactIn, miss, aimedAtBase = true, baseImpact - oneWay, baseMiss, true end -- hold = the flight: displaced from this flick until the shot has -- passed where it could land, and never less than the manual hold, -- so smart can only extend a flick, never shorten one local led = math.max(impactIn or 0, flight) STATE.dodgeFlight = led STATE.dodgeSmartHold = math.max(CONFIG.dodgeHold or 0.4, led + 0.15) STATE.dodgeForce = true -- and the geometry, in the log: how far off the shot's own line the -- body was and how far off the flick leaves it local vflat = Vector3.new(data.velocity.X, 0, data.velocity.Z) if vflat.Magnitude > 0.5 then local perp = Vector3.new(-vflat.Unit.Z, 0, vflat.Unit.X) local wasOff = math.abs((root.Position - data.origin):Dot(perp)) local willBe = math.abs((base + (STATE.dodgeOffset or Vector3.new(0, 0, 0)) - data.origin):Dot(perp)) STATE.offBefore, STATE.offAfter = wasOff, willBe offText = string.format(" | off the shot's line %.1f -> %.1f studs", wasOff, willBe) end end) if not ok then STATE.smartError = tostring(err) STATE.dodgeSmartHold = nil if not STATE.smartErrorLogged or os.clock() - STATE.smartErrorLogged > 2 then STATE.smartErrorLogged = os.clock() logEvent("smart", "smart dodge failed (" .. tostring(err):sub(1, 80) .. ") - taking the manual dodge instead") end end end STATE.armDodge(data.velocity, aimAt) if CONFIG.dodgeSmart and hit ~= nil then local label = tostring(weapon and weapon.id or data.kind) if not STATE.smartLogged or os.clock() - STATE.smartLogged > 1 then STATE.smartLogged = os.clock() logEvent("smart", string.format( "%s %s out: flight %.0fms, %s, holding %.0fms%s%s", label, tostring(data.kind), (flight or 0) * 1000, hit and string.format("arc reaches us in %.0fms%s", impactIn * 1000, aimedAtBase and " (at the base position)" or "") or string.format("arc passes %.1f clear", miss or 0), (STATE.dodgeSmartHold or 0) * 1000, rapid and " (rapid: fresh side per shot)" or "", offText or "")) end end -- Every prediction is kept for a moment afterwards, so when a hit lands -- there is something to check it against: whether the arc called it, and -- whether the body was moved before it arrived. Without that a hit is -- just a hit, and there is no way to tell a model that was wrong from a -- model that was right and a step that came too late. if CONFIG.dodgeSmart then STATE.shotPredictions = STATE.shotPredictions or {} STATE.shotPredictions[#STATE.shotPredictions + 1] = { at = os.clock(), kind = tostring(data.kind), weapon = tostring((STATE.weaponFor(shooterChar, data.kind) or {}).id or data.kind), hit = hit, impactIn = impactIn, dist = near, atUs = root.Position, shooter = shooterChar and shooterChar.Name or "?", } STATE.shotsAtUs = (STATE.shotsAtUs or 0) + 1 CAL.shotsFor = CAL.shotsFor or {} local who = shooterChar and shooterChar.Name or "?" CAL.shotsFor[who] = (CAL.shotsFor[who] or 0) + 1 while #STATE.shotPredictions > 12 do table.remove(STATE.shotPredictions, 1) end end end end end if typeof(data.origin) == "Vector3" and typeof(data.velocity) == "Vector3" and type(data.key) == "string" then local existing = CAL.pending[data.key] local rec = existing or {} rec.origin, rec.velocity = data.origin, data.velocity rec.gravity = tonumber(data.gravity) or 0 rec.kind = data.kind or rec.kind rec.at = os.clock() rec.mine = (data.shooter == LP or data.shooter == LP.Character) -- does the server reuse the direction we redirected to? if rec.sentDir then local angle = math.deg(math.acos(math.clamp(rec.sentDir:Dot(data.velocity.Unit), -1, 1))) if angle > 2 then CAL.dirMismatch = (CAL.dirMismatch or 0) + 1 end end if rec.sentOrigin and typeof(data.origin) == "Vector3" then local off = (rec.sentOrigin - data.origin).Magnitude CAL.originDelta = off CAL.originChecked = (CAL.originChecked or 0) + 1 if off > 0.5 then CAL.originMismatch = (CAL.originMismatch or 0) + 1 end end CAL.pending[data.key] = rec for key, rec in pairs(CAL.pending) do if os.clock() - rec.at > 10 then CAL.pending[key] = nil end end end elseif action == "ProjectileHit" then -- A shake covers the window from their draw to their shot landing, so the landing is -- where it ends. This lives inside the impact branch that already exists: an extra -- branch of my own ahead of this one in the same chain would match first and make -- everything below it - tracing, impact markers, the impact accounting - unreachable. if STATE.jitterUntil and os.clock() < STATE.jitterUntil then STATE.jitterUntil = os.clock() if not STATE.jitterStopLogged or os.clock() - STATE.jitterStopLogged > 1 then STATE.jitterStopLogged = os.clock() logEvent("jitter", string.format("their shot landed: shake ended after %.0fms, %d positions", (os.clock() - (STATE.jitterStarted or os.clock())) * 1000, STATE.jitterTicks or 0)) end end -- one shot can report several impacts (the server emits "id/1", "id/2", ...) local hitKey = data.key if type(hitKey) == "string" then local base = hitKey:match("^(.-)/%d+$") if base then hitKey = base end end local rec = hitKey and CAL.pending[hitKey] local position = typeof(data.position) == "Vector3" and data.position or nil STATE.hitEvents = (STATE.hitEvents or 0) + 1 if not rec then STATE.hitsNoRecord = (STATE.hitsNoRecord or 0) + 1 end if not position then -- an impact with no position cannot be traced or marked; keep the event -- itself so the fields the server did send can be inspected STATE.hitsNoPos = (STATE.hitsNoPos or 0) + 1 STATE.lastNoPos = data end -- one hit can arrive as several impacts; a tracer or marker per impact would -- stack them on top of each other, so only the first of a key is drawn if position then local mine = (hitKey and STATE.myKeys and STATE.myKeys[hitKey]) and true or (rec and rec.mine) or false if mine then STATE.ourHits = (STATE.ourHits or 0) + 1 end local wanted = mine and VIS.tracers or (VIS.tracers and VIS.tracersEnemy) logEvent("fx", string.format("hit key=%s mine=%s rec=%s fireOrigin=%s origin=%s wanted=%s", tostring(hitKey), tostring(mine), tostring(rec ~= nil), tostring(rec and rec.fireOrigin and "yes" or "no"), tostring(rec and rec.origin and "yes" or "no"), tostring(wanted))) if not wanted then STATE.tracerSkipped = (STATE.tracerSkipped or 0) + 1 end if wanted then local from = rec and (rec.fireOrigin or rec.origin) if typeof(from) ~= "Vector3" then STATE.tracerNoOrigin = (STATE.tracerNoOrigin or 0) + 1 end local tracerFn = STATE.fxTracer if tracerFn then local ok, err = pcall(tracerFn, from, position, mine and VIS.tracerColor or VIS.tracerColorEnemy, VIS.tracerLife, VIS.tracerThickness) if not ok then logEvent("fx", "tracer error: " .. tostring(err)) end end end if VIS.markers and (mine or VIS.tracersEnemy) then local hitPlayer = data.character ~= nil and typeof(data.character) == "Instance" local markerFn = STATE.fxMarker if markerFn then local ok, err = pcall(markerFn, position, hitPlayer and VIS.markerColorPlayer or VIS.markerColorWorld, VIS.markerSize, VIS.markerLife) if not ok then logEvent("fx", "marker error: " .. tostring(err)) end end end end -- 1. measured physics. Needs the launch state the server broadcast, and a -- flight long enough that the collision-step overshoot is negligible: -- fast bolts read a 36 studs/s^2 gravity against a claimed 28, slow -- arrows read 55.5 against 55, so only the latter is trustworthy. if rec and rec.origin and rec.velocity and position then local t, g, residual = solveTrajectory(rec.origin, rec.velocity, position) if t and t >= 0.3 and g then CAL.samples = CAL.samples + 1 CAL.lastG, CAL.lastT = g, t local kind = rec.kind or "?" -- real launch speed from the server's own broadcast if rec.velocity then CAL.speedSamples = CAL.speedSamples or {} local sp = CAL.speedSamples[kind] or {} sp[#sp + 1] = rec.velocity.Magnitude if #sp > 80 then table.remove(sp, 1) end CAL.speedSamples[kind] = sp end CAL.gCount[kind] = (CAL.gCount[kind] or 0) + 1 CAL.residuals[kind] = CAL.residuals[kind] or {} local list = CAL.residuals[kind] list[#list + 1] = residual if #list > 60 then table.remove(list, 1) end local gs = CAL.gSamples and CAL.gSamples[kind] if not CAL.gSamples then CAL.gSamples = {} end gs = CAL.gSamples[kind] or {} gs[#gs + 1] = g if #gs > 60 then table.remove(gs, 1) end CAL.gSamples[kind] = gs CAL.gClaimed = CAL.gClaimed or {} local claimed = CAL.gClaimed[kind] or {} claimed[#claimed + 1] = rec.gravity or 0 if #claimed > 60 then table.remove(claimed, 1) end CAL.gClaimed[kind] = claimed if #gs >= 12 then CAL.gClaimedMedian = CAL.gClaimedMedian or {} CAL.gClaimedMedian[kind] = STATE.median(claimed) local spread = table.clone(gs) table.sort(spread) local lo = spread[math.max(1, math.floor(#spread * 0.25))] local hi = spread[math.min(#spread, math.floor(#spread * 0.75) + 1)] local mid = STATE.median(gs) CAL.gSpread = CAL.gSpread or {} CAL.gSpread[kind] = mid > 0 and (hi - lo) / mid or 1 CAL.gByKind[kind] = (CAL.gSpread[kind] <= 0.15) and mid or nil end end end -- 2. own-shot quality, scored once per shot if rec and not rec.scored and position then rec.scored = true -- solve the server's actual launch speed from this impact if rec.sentDir and rec.sentOrigin and rec.mine then local solved = solveLaunchSpeed(rec.sentOrigin, rec.sentDir, position, rec.assumedGravity or (rec.gravity or 0)) if solved then CAL.solvedSpeed = CAL.solvedSpeed or {} local list = CAL.solvedSpeed[rec.kind or "?"] or {} list[#list + 1] = solved if #list > 100 then table.remove(list, 1) end CAL.solvedSpeed[rec.kind or "?"] = list logEvent("speed", string.format("solved launch speed %.1f (assumed %.1f) from a %.1f stud shot", solved, rec.weaponSpeed or -1, (position - rec.sentOrigin).Magnitude)) end end -- prediction error, independent of the hitbox: how far the target was -- from where the solver said it would be at impact, and which way if false then local tRoot = rec.model:FindFirstChild("HumanoidRootPart") if tRoot then local err = tRoot.Position - rec.predictAt local signed = rec.velDir and err:Dot(rec.velDir) or 0 CAL.predSum = (CAL.predSum or 0) + err.Magnitude CAL.predAlongSum = (CAL.predAlongSum or 0) + signed CAL.predCount = (CAL.predCount or 0) + 1 -- Landing offset only counts when the arrow actually reached -- the target. Arrows that stopped on a wall say nothing about -- the prediction, they just make the average look over-led. -- Three outcomes, not two: hitting a DIFFERENT player means the -- shot connected, it just had someone else in the way. local hitChar = data.character local isTarget = (hitChar == rec.model) local isOther = (not isTarget) and hitChar ~= nil and typeof(hitChar) == "Instance" local landed = isTarget if landed then local off = (position - rec.predictAt) local advance = rec.velDir and off:Dot(rec.velDir) or 0 local lateral = rec.velDir and (off - rec.velDir * advance).Magnitude or off.Magnitude -- We sample the target when the impact event reaches us, so -- it has kept moving for one-way latency since the hit. local delay = oneWayLatency() + (CONFIG.viewDelay or 0) local speed = rec.targetSpeed or 0 local bias = advance - speed * delay CAL.latSum = (CAL.latSum or 0) + lateral CAL.latCount = (CAL.latCount or 0) + 1 if rec.myMoving then CAL.latMoveSum = (CAL.latMoveSum or 0) + lateral CAL.latMoveCount = (CAL.latMoveCount or 0) + 1 else CAL.latStillSum = (CAL.latStillSum or 0) + lateral CAL.latStillCount = (CAL.latStillCount or 0) + 1 end CAL.hitSum = (CAL.hitSum or 0) + bias CAL.hitCount = (CAL.hitCount or 0) + 1 CAL.recent = CAL.recent or {} CAL.recent[#CAL.recent + 1] = 1 if #CAL.recent > 20 then table.remove(CAL.recent, 1) end -- does our own movement degrade the shot? split the landing -- error by whether the shooter was moving when it fired if rec.myMoving then CAL.movingSum = (CAL.movingSum or 0) + bias CAL.movingCount = (CAL.movingCount or 0) + 1 else CAL.stillSum = (CAL.stillSum or 0) + bias CAL.stillCount = (CAL.stillCount or 0) + 1 end CAL.hitAbsSum = (CAL.hitAbsSum or 0) + off.Magnitude else CAL.missSum = (CAL.missSum or 0) + signed CAL.missCount = (CAL.missCount or 0) + 1 CAL.recent = CAL.recent or {} CAL.recent[#CAL.recent + 1] = isOther and 1 or 0 if #CAL.recent > 20 then table.remove(CAL.recent, 1) end CAL.otherHits = (CAL.otherHits or 0) + (isOther and 1 or 0) CAL.worldHits = (CAL.worldHits or 0) + (isOther and 0 or 1) end -- did the target actually travel as far as we assumed? This is -- the direct check on the velocity estimate driving the lead. local ratio = -1 if rec.fireTargetPos and rec.flight and rec.flight > 0.02 then local moved = tRoot.Position - rec.fireTargetPos local elapsed = (rec.flight or 0) + 0.4 if moved.Magnitude > 60 * math.max(0.1, elapsed) then moved = Vector3.new(0, 0, 0) -- stale/recycled model end local advance = rec.velDir and moved:Dot(rec.velDir) or 0 -- subtract the target's travel during event delivery advance = advance - (rec.targetSpeed or 0) * (oneWayLatency() + (CONFIG.viewDelay or 0)) local predicted = (rec.targetSpeed or 0) * rec.flight if predicted > 0.5 then ratio = advance / predicted CAL.ratioSum = (CAL.ratioSum or 0) + ratio CAL.ratioCount = (CAL.ratioCount or 0) + 1 end end logEvent("prederr", string.format("%s %s |err|=%.2f along=%+.2f advRatio=%.2f flight=%.3f spd=%.1f", tostring(rec.model.Name), landed and "HIT " or "wall", err.Magnitude, signed, ratio, rec.flight or 0, rec.targetSpeed or 0)) end end -- lead quality: only meaningful when the arrow actually landed on the -- target, measured along the target's own direction of travel local hitChar = data.character if rec.velDir and hitChar and typeof(hitChar) == "Instance" then local root = hitChar:FindFirstChild("HumanoidRootPart") local speed = rec.targetSpeed or 0 if root and speed > 2 then local along = (position - root.Position):Dot(rec.velDir) / speed if math.abs(along) < 0.4 then local w = rec.weaponId or "?" CAL.leadSum = CAL.leadSum or {} CAL.leadSum[w] = (CAL.leadSum[w] or 0) + along CAL.leadCount[w] = (CAL.leadCount[w] or 0) + 1 CAL.leadByWeapon[w] = CAL.leadSum[w] / CAL.leadCount[w] end end end end -- 3. where our own arrow ended up local seen = STATE.myKeys and hitKey and STATE.myKeys[hitKey] if seen and os.clock() - seen < 8 then local miss = -1 local ch = data.character if ch and typeof(ch) == "Instance" then local r = ch:FindFirstChild("HumanoidRootPart") or ch:FindFirstChild("Head") if r then miss = (r.Position - position).Magnitude end end logEvent("impact", string.format("char=%s part=%s miss=%.2f expired=%s", tostring(ch and ch.Name), tostring(data.part and data.part.Name), miss, tostring(data.expired))) STATE.lastImpact = { character = data.character, position = data.position, part = data.part } end elseif action == "Hit" then local victim = data.target if victim == LP or victim == LP.Character then -- incoming fire that connected, split by whether the desync was live: -- this is the only direct read on whether it is actually taking shots CAL.onUs = CAL.onUs or { on = 0, onDmg = 0, off = 0, offDmg = 0 } local dmg = tonumber(data.damage or data.amount) or 0 -- Match this hit against the last prediction made for a shot at us, and say which -- way it went wrong: the arc called a landing and the move did not save us, or -- the arc called a miss and it landed. CAL.taken = (CAL.taken or 0) + 1 do local prediction for index = #(STATE.shotPredictions or {}), 1, -1 do local entry = STATE.shotPredictions[index] if os.clock() - entry.at < 1.5 then prediction = entry break end end CAL.takenFor = CAL.takenFor or {} local attackerName local attacker = data.attacker if attacker == LP or attacker == LP.Character then attackerName = nil elseif typeof(attacker) == "Instance" then attackerName = (attacker:IsA("Player") and attacker.Name) or attacker.Name end if prediction and prediction.shooter then attackerName = attackerName or prediction.shooter end if attackerName then CAL.takenFor[attackerName] = (CAL.takenFor[attackerName] or 0) + 1 end if attackerName then CAL.takenPredictedHit = CAL.takenPredictedHit or 0 end if not prediction then logEvent("taken", string.format("hit for %.0f with no shot prediction on file", dmg)) elseif prediction.hit then CAL.takenPredictedHit = (CAL.takenPredictedHit or 0) + 1 logEvent("taken", string.format( "%s at %.0f studs: arc said it lands (%.0fms away), %s - and it still hit", prediction.weapon, prediction.dist or 0, (prediction.impactIn or 0) * 1000, prediction.moved and string.format("moved %.0fms before it arrived", (prediction.moveIn or 0) * 1000) or "no move was made")) else CAL.takenPredictedMiss = (CAL.takenPredictedMiss or 0) + 1 logEvent("taken", string.format( "%s at %.0f studs: arc said it passes clear and it hit anyway - the arc was wrong", prediction.weapon, prediction.dist or 0)) end end if CONFIG.desync then CAL.onUs.on = CAL.onUs.on + 1 CAL.onUs.onDmg = CAL.onUs.onDmg + dmg else CAL.onUs.off = CAL.onUs.off + 1 CAL.onUs.offDmg = CAL.onUs.offDmg + dmg end local measured = sampleViewDelay(data.position) if measured then CAL.delaySum = (CAL.delaySum or 0) + measured CAL.delayCount = (CAL.delayCount or 0) + 1 logEvent("delay", string.format("impact on us -> view delay %.3fs (mean %.3fs, n=%d)", measured, CAL.delaySum / CAL.delayCount, CAL.delayCount)) end end local attacker = data.attacker if attacker == LP or attacker == LP.Character then logEvent("hit", string.format("dmg=%s head=%s blocked=%s crit=%s", tostring(data.damage or data.amount), tostring(data.headshot), tostring(data.blocked), tostring(data.crit))) end end end local denialConnection = ReplicatedStorage.Remotes.CombatFX.OnClientEvent:Connect(onCombatFXEvent) ;(function() local unreliable = ReplicatedStorage.Remotes:FindFirstChild("CombatFXU") if unreliable and unreliable:IsA("UnreliableRemoteEvent") then STATE.unreliableFx = unreliable.OnClientEvent:Connect(onCombatFXEvent) end end)(); local hitConnection = ReplicatedStorage.Remotes.HitConfirm.OnClientEvent:Connect(function(data) if type(data) == "table" then STATE.lastHit = { headshot = data.headshot, amount = data.amount, victim = data.victim, id = data.id, at = os.clock(), killed = data.killed } logEvent("confirm", string.format("victim=%s dmg=%s head=%s kill=%s", tostring(data.victim), tostring(data.amount), tostring(data.headshot), tostring(data.killed))) -- Our own hit sound, deliberately not the names the game already plays on this -- same event: util.Sound drops a repeat of the same name inside 35ms, so using -- "Hitmarker_Ranged" here would be swallowed by the game's own hitmarker. if VIS.hitsounds then local name = data.killed and "Hit_Melee_Heavy" or (data.headshot and "Hit_Punch" or "Hit_Melee") pcall(function() Util.Sound(name, nil, { Volume = math.clamp((VIS.hitsoundVolume or 60) / 100, 0, 1) }) end) end end end) local UI = { theme = { bg = Color3.fromRGB(23, 24, 26), panel = Color3.fromRGB(32, 33, 36), panelHeader = Color3.fromRGB(41, 43, 47), border = Color3.fromRGB(9, 9, 10), borderLight = Color3.fromRGB(58, 60, 64), select = Color3.fromRGB(48, 50, 55), hover = Color3.fromRGB(44, 46, 50), text = Color3.fromRGB(205, 208, 212), bright = Color3.fromRGB(236, 239, 243), dim = Color3.fromRGB(128, 132, 138), accent = Color3.fromRGB(222, 226, 231), accentDim = Color3.fromRGB(92, 96, 102), track = Color3.fromRGB(16, 17, 19), fill = Color3.fromRGB(74, 78, 84), off = Color3.fromRGB(58, 60, 64), } } local function container() -- nested on purpose: the main chunk sits at its local limit, and this is only -- needed while picking a container local function canParent(host) if not host then return false end return (pcall(function() local probe = Instance.new("Frame") probe.Name = "TribesGuiProbe" probe.Parent = host probe:Destroy() end)) end local ok, hui = pcall(gethui) if ok and typeof(hui) == "Instance" and canParent(hui) then return hui end local core = game:GetService("CoreGui") if canParent(core) then return core end local playerGui = game:GetService("Players").LocalPlayer:FindFirstChildOfClass("PlayerGui") if playerGui then return playerGui end return core end local function prop(obj, props) for k, v in pairs(props) do local ok = pcall(function() obj[k] = v end) if not ok then STATE.setPropFails = (STATE.setPropFails or 0) + 1 STATE.lastPropFail = tostring(k) end end return obj end -- Some environments refuse instance writes from certain threads with "lacking -- capability Plugin". Nothing here can change that, so every step is attempted and -- a refusal is survivable: the object still exists and is still usable, it just -- misses the property that was refused. If the object cannot be parented where it -- was asked to go, it is parked in the player's own PlayerGui instead, which is -- writable everywhere, so the interface still appears. local function mk(class, props, parent) local obj = Instance.new(class) if props then pcall(prop, obj, props) end local ok = pcall(function() obj.Parent = parent end) if not ok or obj.Parent ~= parent then -- parked in the player's own PlayerGui instead: writable everywhere, so the -- interface still appears even where that parent is refused if not (STATE.fallbackHost and STATE.fallbackHost.Parent) then local playerGui = game:GetService("Players").LocalPlayer:FindFirstChildOfClass("PlayerGui") if playerGui then local host = Instance.new("ScreenGui") host.Name = "TribesFallback" host.IgnoreGuiInset = true host.ResetOnSpawn = false host.DisplayOrder = 2147482000 host.Parent = playerGui STATE.fallbackHost = host end end if STATE.fallbackHost then pcall(function() obj.Parent = STATE.fallbackHost end) end end return obj end local function round(obj, r) local c = Instance.new("UICorner") c.CornerRadius = UDim.new(0, r or 0) c.Parent = obj return c end local function outline(obj, color, thickness, transparency) local s = Instance.new("UIStroke") s.Color = color or UI.theme.border s.Thickness = thickness or 1 s.Transparency = transparency or 0 s.ApplyStrokeMode = Enum.ApplyStrokeMode.Border s.Parent = obj return s end -- the frame edge: a black hairline plus a faint inner highlight, which is what -- gives the flat panels their shape local function padding(obj, top, bottom, left, right) local pad = Instance.new("UIPadding") pad.PaddingTop = UDim.new(0, top or 0) pad.PaddingBottom = UDim.new(0, bottom or 0) pad.PaddingLeft = UDim.new(0, left or 0) pad.PaddingRight = UDim.new(0, right or 0) pad.Parent = obj return pad end ;(function() local host = container() for _, leftover in ipairs(host:GetChildren()) do if leftover:IsA("ScreenGui") and (leftover.Name == "TribesCSGO" or leftover.Name == "TribesESP") then pcall(function() leftover:Destroy() end) end end end)(); local sg = mk("ScreenGui", { Name = "TribesCSGO", IgnoreGuiInset = true, ResetOnSpawn = false, DisplayOrder = 2147483647, ZIndexBehavior = Enum.ZIndexBehavior.Sibling, }, container()) pcall(function() if protectgui then protectgui(sg) end end) -- ------------------------------------------------------------------ settings persistence -- Every control writes straight through to disk, so there is no save button to -- forget and no state that only lives until the next respawn. The write is -- debounced because a slider drag fires on every frame. local CONFIG_FILE = "tribes_overlay_config.json" local HttpService = game:GetService("HttpService") local settingsDirty = false local function configSnapshot() local out = {} -- keys a test has set are excluded: without this a save issued while a test is -- running writes the test's values into the file, and they survive the reload local pinned = STATE.testPins local function take(prefix, source) for key, value in pairs(source) do -- the lead scale is pinned in code, not a setting if not (pinned and pinned[key] ~= nil) then local kind = typeof(value) if kind == "number" or kind == "string" or kind == "boolean" then out[prefix .. key] = value elseif kind == "Color3" then out[prefix .. key] = { __color = true, r = value.R, g = value.G, b = value.B } end end end end take("c_", CONFIG) take("v_", VIS) return out end local function saveConfig() if type(writefile) ~= "function" then return false, "no filesystem access" end local snapshot = configSnapshot() local ok, encoded = pcall(function() return HttpService:JSONEncode(snapshot) end) if not ok or type(encoded) ~= "string" then return false, "encode failed: " .. tostring(encoded) end local written, err = pcall(writefile, CONFIG_FILE, encoded) if not written then return false, "write failed: " .. tostring(err) end local n = 0 for _ in pairs(snapshot) do n += 1 end return true, string.format("saved %d settings", n) end local function loadConfig() if type(readfile) ~= "function" then return false, "no filesystem access" end local ok, data = pcall(readfile, CONFIG_FILE) if not ok or type(data) ~= "string" or #data == 0 then return false, "no saved config" end local decoded ok, decoded = pcall(function() return HttpService:JSONDecode(data) end) if not ok or type(decoded) ~= "table" then return false, "saved config is corrupt" end local applied, skipped = 0, 0 for key, value in pairs(decoded) do local prefix, name = key:sub(1, 1), key:sub(3) local target = (prefix == "c" and CONFIG) or (prefix == "v" and VIS) or nil -- not `target and name ~= "" and target[name] or nil`: a stored false would -- collapse to nil there and every toggle that was off would be treated as -- an unknown key and skipped local current if target and name ~= "" then current = target[name] end if current == nil then skipped += 1 elseif typeof(current) == "Color3" then if type(value) == "table" and value.__color then target[name] = Color3.new(value.r or 0, value.g or 0, value.b or 0) applied += 1 else skipped += 1 end elseif type(value) == type(current) then target[name] = value applied += 1 else skipped += 1 end end -- pinned values win over whatever an older file happens to hold, and the -- displacement sliders are clamped: a saved 14-stud dodge makes a jump of nearly -- thirty studs once it stacks with the desync CONFIG.viewDelay = math.clamp(tonumber(CONFIG.viewDelay) or 0, 0, 0.2) CONFIG.leadScale = math.clamp(tonumber(CONFIG.leadScale) or 1, 0, 2) CONFIG.dodgeDistanceMin = math.clamp(tonumber(CONFIG.dodgeDistanceMin) or 6, 2, 40) CONFIG.dodgeDistanceMax = math.clamp(tonumber(CONFIG.dodgeDistanceMax) or 10, 2, 40) if CONFIG.dodgeDistanceMin > CONFIG.dodgeDistanceMax then CONFIG.dodgeDistanceMin, CONFIG.dodgeDistanceMax = CONFIG.dodgeDistanceMax, CONFIG.dodgeDistanceMin end CONFIG.desyncAmount = math.clamp(tonumber(CONFIG.desyncAmount) or 5, 0, 12) CONFIG.desyncVerticalAmount = math.clamp(tonumber(CONFIG.desyncVerticalAmount) or 2.5, 0, 6) CONFIG.desyncJitter = math.clamp(tonumber(CONFIG.desyncJitter) or 0.25, 0, 0.6) CONFIG.meleeHopHeight = math.clamp(tonumber(CONFIG.meleeHopHeight) or 10, 4, 20) -- a hold of zero is allowed: the step then lands and is released on the same frame, -- which is a dodge that exists only as a flick CONFIG.dodgeHold = math.clamp(tonumber(CONFIG.dodgeHold) or 0.4, 0, 1.5) CONFIG.dodgeRange = math.clamp(tonumber(CONFIG.dodgeRange) or 55, 10, 200) CONFIG.realBodyScale = math.clamp(tonumber(CONFIG.realBodyScale) or 1, 0.5, 2.5) CONFIG.jitterMin = math.clamp(tonumber(CONFIG.jitterMin) or 3, 1, 30) CONFIG.jitterMax = math.clamp(tonumber(CONFIG.jitterMax) or 15, 1, 30) CONFIG.jitterInterval = math.clamp(tonumber(CONFIG.jitterInterval) or 0.01, 0.003, 0.1) return true, string.format("loaded %d settings (%d skipped)", applied, skipped) end local function saveSoon() settingsDirty = true end task.spawn(function() while not closed do if settingsDirty then settingsDirty = false pcall(saveConfig) end task.wait(0.5) end end) -- applied before any row is built, so the menu opens on the saved state loadConfig() -- ------------------------------------------------------------------ interface (uip100) -- The menu is the uip100 library. Every control here is a thin pair of accessors over -- CONFIG and VIS, so the aim engine, the ESP and the hvH sections keep reading those -- two tables exactly as they did with the built-in menu. The library also keeps its own -- config files (Library's Configs tab) and trident keeps its JSON, both under uip100/. local uiWindow, uiRefresh -- called from the render loop inside the interface block below, so it is declared here local updateWallbang do local Library = loadstring(game:HttpGet("https://pastebin.com/raw/ncKGkPTW"))() Library.Folders = { Directory = "uip100", Configs = "uip100/Configs", Assets = "uip100/Assets", } for _, Folder in pairs(Library.Folders) do if not isfolder(Folder) then makefolder(Folder) end end pcall(function() Library:ChangeTheme("Accent", Color3.fromRGB(0, 190, 180)) end) local Window = Library:Window({ Name = "Trident", Logo = "90829033870443", MobileButtonText = "Trident" }) local Watermark = Library:Watermark("trident") local KeybindList = Library:KeybindList() local TargetHud = Library:TargetHud() TargetHud:SetPlayer(LP) local HealthBar = TargetHud:AddBar(Color3.fromRGB(70, 200, 90)) local HitBar = TargetHud:AddBar(Color3.fromRGB(0, 190, 180)) -- the library's target hud wants a Player for the avatar and the name; the aim target -- is just as often a training dummy or a bot, so its label is written directly local HudName, HudAvatar do local holder = Library.Holder and Library.Holder.Instance for _, item in ipairs(holder and holder:GetDescendants() or {}) do if item:IsA("TextLabel") and item.Text and item.Text:find("@", 1, true) then HudName = item for _, sibling in ipairs(item.Parent:GetChildren()) do if sibling:IsA("ImageLabel") then HudAvatar = sibling end end break end end end local function HudTarget() local model = STATE.lock and STATE.lock.model if not (model and model.Parent) then model = STATE.lastSolution and STATE.lastSolution.model end if model and model.Parent then return model end return nil end -- Every element is registered with its accessor so one pass puts the whole menu -- back on the CONFIG/VIS state after a config load or a reset. local Controls = {} local function track(Element, Get) Controls[#Controls + 1] = { Element = Element, Get = Get } return Element end uiRefresh = function() for _, Entry in ipairs(Controls) do pcall(function() Entry.Element:Set(Entry.Get()) end) end end local function Section(Page, Name, Side) return Page:Section({ Name = Name, Side = Side }) end local function Toggle(Parent, Name, Flag, Get, Set) return track(Parent:Toggle({ Name = Name, Flag = Flag, Default = Get(), Callback = function(Value) Set(Value) saveSoon() end, }), Get) end local function Slider(Parent, Name, Flag, Min, Max, Get, Set, Decimals, Suffix) return track(Parent:Slider({ Name = Name, Flag = Flag, Min = Min, Max = Max, Decimals = Decimals or 0.01, Suffix = Suffix or "", Default = Get(), Callback = function(Value) Set(Value) saveSoon() end, }), Get) end local function Dropdown(Parent, Name, Flag, Items, Get, Set) return track(Parent:Dropdown({ Name = Name, Flag = Flag, Items = Items, Default = Get(), Callback = function(Value) Set(Value) saveSoon() end, }), Get) end local function Colour(Parent, Name, Flag, Get, Set) return track(Parent:Label(Name):Colorpicker({ Flag = Flag, Default = Get(), Callback = function(Value) Set(Value) saveSoon() end, }), Get) end -- A keybind attached to a toggle fires that toggle while the element is still being -- built (the key starts off), so the stored value is put back afterwards. local function Keyed(Element, Flag, Key, Mode, Get) local Value = Get() Element:Keybind({ Flag = Flag, Default = Key, Mode = Mode }) Element:Set(Value) return Element end local function Lower(Value) return string.lower(tostring(Value)) end -- dropdowns carry option labels; CONFIG keeps the lower-case api values local Labels = { targets = { dummies = "Dummies", players = "Players", both = "Both" }, playerHitPart = { auto = "Auto", head = "Head", body = "Body" }, dummyHitPart = { auto = "Auto", head = "Head", body = "Body" }, mode = { direct = "Direct", mortar = "Mortar" }, } local Combat = Window:Page({ Name = "Combat", Columns = 2 }) local Visuals = Window:Page({ Name = "Visuals", Columns = 2 }) local Misc = Window:Page({ Name = "Misc", Columns = 2 }) -- -------------------------------- combat: aim local Aim = Section(Combat, "Silent aim", 1) Keyed(Toggle(Aim, "Enable aim assist", "C_enabled", function() return CONFIG.enabled end, function(Value) CONFIG.enabled = Value end), "K_aim", Enum.KeyCode.X, "Toggle", function() return CONFIG.enabled end) Toggle(Aim, "Wait for target visible", "C_simulate", function() return CONFIG.simulate end, function(Value) CONFIG.simulate = Value end) Toggle(Aim, "Gravity compensation", "C_gravityComp", function() return CONFIG.gravityComp end, function(Value) CONFIG.gravityComp = Value end) Toggle(Aim, "Prediction (velocity lead)", "C_predict", function() return CONFIG.predict end, function(Value) CONFIG.predict = Value end) Slider(Aim, "Lead scale", "C_leadScale", 0, 2, function() return CONFIG.leadScale end, function(Value) CONFIG.leadScale = Value end, 0.01, "x") Toggle(Aim, "Wallbang (move shot origin)", "C_wallbang", function() return CONFIG.wallbang end, function(Value) CONFIG.wallbang = Value end) Slider(Aim, "Wallbang reach", "C_wallbangMax", 2, 150, function() return CONFIG.wallbangMax end, function(Value) CONFIG.wallbangMax = Value end, 1, " studs") Dropdown(Aim, "Targets", "C_targets", { "Dummies", "Players", "Both" }, function() return Labels.targets[CONFIG.targets] or "Both" end, function(Value) CONFIG.targets = Lower(Value) end) Dropdown(Aim, "Player hitbox", "C_playerHitPart", { "Auto", "Head", "Body" }, function() return Labels.playerHitPart[CONFIG.playerHitPart] or "Auto" end, function(Value) CONFIG.playerHitPart = Lower(Value) end) Dropdown(Aim, "Dummy hitbox", "C_dummyHitPart", { "Auto", "Head", "Body" }, function() return Labels.dummyHitPart[CONFIG.dummyHitPart] or "Auto" end, function(Value) CONFIG.dummyHitPart = Lower(Value) end) Dropdown(Aim, "Trajectory", "C_mode", { "Direct", "Mortar" }, function() return Labels.mode[CONFIG.mode] or "Direct" end, function(Value) CONFIG.mode = Lower(Value) end) Slider(Aim, "FOV / aim cone", "C_fov", 5, 180, function() return CONFIG.fov end, function(Value) CONFIG.fov = Value end, 1, " deg") Aim:Label("target = closest to the crosshair in the circle") Aim:Label("lead = their travel over the flight x scale") Toggle(Aim, "Instant draw (full charge)", "C_instantFire", function() return CONFIG.instantFire end, function(Value) CONFIG.instantFire = Value end) Keyed(Toggle(Aim, "Auto-fire when server ready", "C_autoFire", function() return CONFIG.autoFire end, function(Value) CONFIG.autoFire = Value end), "K_autoFire", Enum.KeyCode.C, "Toggle", function() return CONFIG.autoFire end) -- -------------------------------- combat: support + status local Support = Section(Combat, "Support", 2) Toggle(Support, "Melee kill aura", "C_killAura", function() return CONFIG.killAura end, function(Value) CONFIG.killAura = Value end) Slider(Support, "Kill aura range", "C_killAuraRange", 0, 20, function() return CONFIG.killAuraRange end, function(Value) CONFIG.killAuraRange = Value end, 0.1, " studs") Toggle(Support, "Auto-heal with bandages", "C_autoHeal", function() return CONFIG.autoHeal end, function(Value) CONFIG.autoHeal = Value end) Slider(Support, "Heal below", "C_autoHealBelow", 5, 95, function() return CONFIG.autoHealBelow end, function(Value) CONFIG.autoHealBelow = Value end, 1, "%") Slider(Support, "Heal cooldown", "C_autoHealCooldown", 1, 15, function() return CONFIG.autoHealCooldown end, function(Value) CONFIG.autoHealCooldown = Value end, 0.1, " s") Toggle(Support, "Team check (skip own tribe)", "C_teamCheck", function() return CONFIG.teamCheck end, function(Value) CONFIG.teamCheck = Value end) Toggle(Support, "Draw FOV ring", "V_fovRing", function() return VIS.fovRing end, function(Value) VIS.fovRing = Value end) Toggle(Support, "Watermark", "V_watermark", function() return VIS.watermark end, function(Value) VIS.watermark = Value end) Toggle(Support, "Target hud", "V_targetHud", function() return VIS.targetHud end, function(Value) VIS.targetHud = Value end) local Status = Section(Combat, "Status", 2) local TargetLine = Status:Label("target") local AimLine = Status:Label("aim") local PoolLine = Status:Label("pool") local LeadLine = Status:Label("lead") local HitLine = Status:Label("hit rate") local CalLine = Status:Label("calibration") local EnvLine = Status:Label("environment") local ErrLine = Status:Label("errors") -- -------------------------------- visuals: esp local Esp = Section(Visuals, "ESP", 1) Keyed(Toggle(Esp, "Enable ESP", "V_enabled", function() return VIS.enabled end, function(Value) VIS.enabled = Value end), "K_esp", Enum.KeyCode.V, "Toggle", function() return VIS.enabled end) Toggle(Esp, "Wallbang indicator", "V_wbIndicator", function() return VIS.wallbangIndicator end, function(Value) VIS.wallbangIndicator = Value end) Toggle(Esp, "Box", "V_box", function() return VIS.box end, function(Value) VIS.box = Value end) Dropdown(Esp, "Box style", "V_boxStyle", { "Corners", "Full" }, function() return VIS.boxStyle end, function(Value) VIS.boxStyle = Value end) Toggle(Esp, "Filled box", "V_filledBox", function() return VIS.filledBox end, function(Value) VIS.filledBox = Value end) Slider(Esp, "Fill opacity", "V_fillAlpha", 0.5, 0.98, function() return VIS.fillAlpha end, function(Value) VIS.fillAlpha = Value end, 0.01) Toggle(Esp, "Health bar", "V_healthBar", function() return VIS.healthBar end, function(Value) VIS.healthBar = Value end) Toggle(Esp, "Name", "V_name", function() return VIS.name end, function(Value) VIS.name = Value end) Toggle(Esp, "Distance", "V_distance", function() return VIS.distance end, function(Value) VIS.distance = Value end) Toggle(Esp, "Weapon / tag", "V_weapon", function() return VIS.weapon end, function(Value) VIS.weapon = Value end) Toggle(Esp, "Head dot", "V_headDot", function() return VIS.headDot end, function(Value) VIS.headDot = Value end) Toggle(Esp, "Skeleton", "V_skeleton", function() return VIS.skeleton end, function(Value) VIS.skeleton = Value end) Toggle(Esp, "Snapline", "V_snapline", function() return VIS.snapline end, function(Value) VIS.snapline = Value end) Slider(Esp, "Max distance", "V_maxDistance", 100, 2000, function() return VIS.maxDistance end, function(Value) VIS.maxDistance = Value end, 1, " studs") Slider(Esp, "Text size", "V_textSize", 8, 20, function() return VIS.textSize end, function(Value) VIS.textSize = Value end, 1, " px") -- -------------------------------- visuals: effects local Effects = Section(Visuals, "Effects", 2) Toggle(Effects, "Tracers", "V_tracers", function() return VIS.tracers end, function(Value) VIS.tracers = Value end) Toggle(Effects, "Tracers on enemy shots", "V_tracersEnemy", function() return VIS.tracersEnemy end, function(Value) VIS.tracersEnemy = Value end) Colour(Effects, "Tracer colour", "V_tracerColor", function() return VIS.tracerColor end, function(Value) VIS.tracerColor = Value end) Colour(Effects, "Enemy tracer colour", "V_tracerColorEnemy", function() return VIS.tracerColorEnemy end, function(Value) VIS.tracerColorEnemy = Value end) Slider(Effects, "Tracer life", "V_tracerLife", 0.05, 1, function() return VIS.tracerLife end, function(Value) VIS.tracerLife = Value end, 0.001, " s") Slider(Effects, "Tracer thickness", "V_tracerThickness", 0.1, 0.8, function() return VIS.tracerThickness end, function(Value) VIS.tracerThickness = Value end, 0.01) Toggle(Effects, "Impact markers", "V_markers", function() return VIS.markers end, function(Value) VIS.markers = Value end) Colour(Effects, "Marker colour (world)", "V_markerColorWorld", function() return VIS.markerColorWorld end, function(Value) VIS.markerColorWorld = Value end) Colour(Effects, "Marker colour (player)", "V_markerColorPlayer", function() return VIS.markerColorPlayer end, function(Value) VIS.markerColorPlayer = Value end) Slider(Effects, "Marker size", "V_markerSize", 8, 48, function() return VIS.markerSize end, function(Value) VIS.markerSize = Value end, 1, " px") Slider(Effects, "Marker life", "V_markerLife", 0.1, 1.5, function() return VIS.markerLife end, function(Value) VIS.markerLife = Value end, 0.001, " s") Toggle(Effects, "Hitsounds", "V_hitsounds", function() return VIS.hitsounds end, function(Value) VIS.hitsounds = Value end) Slider(Effects, "Hitsound volume", "V_hitsoundVolume", 0, 100, function() return VIS.hitsoundVolume end, function(Value) VIS.hitsoundVolume = Value end, 1, "%") -- -------------------------------- chams local Highlight = Section(Visuals, "Chams (highlight)", 1) Toggle(Highlight, "Chams", "V_chams", function() return VIS.chams end, function(Value) VIS.chams = Value end) Toggle(Highlight, "Through walls", "V_chamsThrough", function() return VIS.chamsThrough end, function(Value) VIS.chamsThrough = Value end) Toggle(Highlight, "Thermal breathing", "V_chamsThermal", function() return VIS.chamsThermal end, function(Value) VIS.chamsThermal = Value end) Toggle(Highlight, "Rainbow fill", "V_chamsRainbow", function() return VIS.chamsRainbow end, function(Value) VIS.chamsRainbow = Value end) Toggle(Highlight, "Fade with distance", "V_chamsFade", function() return VIS.chamsFade end, function(Value) VIS.chamsFade = Value end) Colour(Highlight, "Fill colour", "V_chamsFill", function() return VIS.chamsFill end, function(Value) VIS.chamsFill = Value end) Slider(Highlight, "Fill alpha", "V_chamsFillAlpha", 0, 100, function() return VIS.chamsFillAlpha end, function(Value) VIS.chamsFillAlpha = Value end, 1, "%") Colour(Highlight, "Outline (players/bots)", "V_chamsOutline", function() return VIS.chamsOutline end, function(Value) VIS.chamsOutline = Value end) Colour(Highlight, "Outline (dummies)", "V_chamsOutlineDummy", function() return VIS.chamsOutlineDummy end, function(Value) VIS.chamsOutlineDummy = Value end) Slider(Highlight, "Outline alpha", "V_chamsOutlineAlpha", 0, 100, function() return VIS.chamsOutlineAlpha end, function(Value) VIS.chamsOutlineAlpha = Value end, 1, "%") local Material = Section(Visuals, "Material chams", 2) Toggle(Material, "Chams on body parts", "V_matChams", function() return VIS.matChams end, function(Value) VIS.matChams = Value end) Toggle(Material, "Rainbow body", "V_matChamsRainbow", function() return VIS.matChamsRainbow end, function(Value) VIS.matChamsRainbow = Value end) Dropdown(Material, "Material", "V_matChamsMaterial", { "ForceField", "Neon", "Glass", "Plastic", "SmoothPlastic", "Metal", "Foil", "Ice", "Marble", "Granite", "Slate", "Concrete", "Wood", "Fabric", }, function() return VIS.matChamsMaterial end, function(Value) VIS.matChamsMaterial = Value end) Dropdown(Material, "Pattern", "V_matChamsPattern", { "Static", "Wave", "Pulse", "Noise", "Lightning", "Scanline", "Grid", }, function() return VIS.matChamsPattern end, function(Value) VIS.matChamsPattern = Value end) Colour(Material, "Body colour", "V_matChamsColor", function() return VIS.matChamsColor end, function(Value) VIS.matChamsColor = Value end) Slider(Material, "Body alpha", "V_matChamsAlpha", 0, 1, function() return VIS.matChamsAlpha end, function(Value) VIS.matChamsAlpha = Value end, 0.01) Slider(Material, "Pattern speed", "V_matChamsSpeed", 0.5, 12, function() return VIS.matChamsSpeed end, function(Value) VIS.matChamsSpeed = Value end, 0.1) -- -------------------------------- colours local Palette = Section(Visuals, "Colors", 1) Colour(Palette, "Enemy", "V_enemyColor", function() return VIS.enemyColor end, function(Value) VIS.enemyColor = Value end) Colour(Palette, "Own tribe", "V_teamColor", function() return VIS.teamColor end, function(Value) VIS.teamColor = Value end) Colour(Palette, "Training dummy", "V_dummyColor", function() return VIS.dummyColor end, function(Value) VIS.dummyColor = Value end) Colour(Palette, "Health (low)", "V_healthBad", function() return VIS.healthBad end, function(Value) VIS.healthBad = Value end) Colour(Palette, "Health (full)", "V_healthGood", function() return VIS.healthGood end, function(Value) VIS.healthGood = Value end) Colour(Palette, "Text", "V_textColor", function() return VIS.textColor end, function(Value) VIS.textColor = Value end) -- -------------------------------- real body local RealBody = Section(Visuals, "Real body visualization", 2) Toggle(RealBody, "Real body visualization", "C_realBody", function() return CONFIG.realBody end, function(Value) CONFIG.realBody = Value end) Colour(RealBody, "Marker colour", "V_realBodyColor", function() return VIS.realBodyColor end, function(Value) VIS.realBodyColor = Value end) Toggle(RealBody, "Colour by distance", "C_realBodyHeat", function() return CONFIG.realBodyHeat end, function(Value) CONFIG.realBodyHeat = Value end) Slider(RealBody, "Marker size", "C_realBodyScale", 0.5, 2.5, function() return CONFIG.realBodyScale end, function(Value) CONFIG.realBodyScale = Value end, 0.01, "x") Toggle(RealBody, "Distance label", "C_realBodyTag", function() return CONFIG.realBodyTag end, function(Value) CONFIG.realBodyTag = Value end) Toggle(RealBody, "Crosshair chevron", "C_realBodyChevron", function() return CONFIG.realBodyChevron end, function(Value) CONFIG.realBodyChevron = Value end) -- -------------------------------- misc: hvH local Hvh = Section(Misc, "Anti aim", 1) Toggle(Hvh, "Desync anti-aim", "C_desync", function() return CONFIG.desync end, function(Value) CONFIG.desync = Value end) Slider(Hvh, "Desync amount", "C_desyncAmount", 0, 12, function() return CONFIG.desyncAmount end, function(Value) CONFIG.desyncAmount = Value end, 0.1, " studs") Toggle(Hvh, "Step across their aim", "C_desyncAimAware", function() return CONFIG.desyncAimAware end, function(Value) CONFIG.desyncAimAware = Value end) Toggle(Hvh, "Desync only when walking", "C_desyncWalkOnly", function() return CONFIG.desyncWalkOnly end, function(Value) CONFIG.desyncWalkOnly = Value end) Slider(Hvh, "Desync interval", "C_desyncInterval", 0.05, 0.6, function() return CONFIG.desyncInterval end, function(Value) CONFIG.desyncInterval = Value end, 0.001, " s") Toggle(Hvh, "Step in height too", "C_desyncVertical", function() return CONFIG.desyncVertical end, function(Value) CONFIG.desyncVertical = Value end) Slider(Hvh, "Height step", "C_desyncVerticalAmount", 0, 6, function() return CONFIG.desyncVerticalAmount end, function(Value) CONFIG.desyncVerticalAmount = Value end, 0.1, " studs") Slider(Hvh, "Interval variation", "C_desyncJitter", 0, 0.6, function() return CONFIG.desyncJitter end, function(Value) CONFIG.desyncJitter = Value end, 0.01) Slider(Hvh, "Local view compensation", "C_desyncLocalSteady", 0, 100, function() return CONFIG.desyncLocalSteady end, function(Value) CONFIG.desyncLocalSteady = Value end, 1, "%") -- -------------------------------- misc: dodge local Dodge = Section(Misc, "Dodge", 2) Toggle(Dodge, "Dodge when attacked", "C_dodge", function() return CONFIG.dodge end, function(Value) CONFIG.dodge = Value end) Toggle(Dodge, "Jitter dodge (draw to landing)", "C_jitterDodge", function() return CONFIG.jitterDodge end, function(Value) CONFIG.jitterDodge = Value end) Slider(Dodge, "Jitter studs min", "C_jitterMin", 1, 30, function() return CONFIG.jitterMin end, function(Value) CONFIG.jitterMin = Value end, 0.1, " studs") Slider(Dodge, "Jitter studs max", "C_jitterMax", 1, 30, function() return CONFIG.jitterMax end, function(Value) CONFIG.jitterMax = Value end, 0.1, " studs") Slider(Dodge, "Jitter every", "C_jitterInterval", 0.003, 0.1, function() return CONFIG.jitterInterval end, function(Value) CONFIG.jitterInterval = Value end, 0.001, " s") Toggle(Dodge, "Smart dodge (their weapon and distance)", "C_dodgeSmart", function() return CONFIG.dodgeSmart end, function(Value) CONFIG.dodgeSmart = Value end) Slider(Dodge, "Dodge studs min", "C_dodgeDistanceMin", 2, 40, function() return CONFIG.dodgeDistanceMin end, function(Value) CONFIG.dodgeDistanceMin = Value end, 0.1, " studs") Slider(Dodge, "Dodge studs max", "C_dodgeDistanceMax", 2, 40, function() return CONFIG.dodgeDistanceMax end, function(Value) CONFIG.dodgeDistanceMax = Value end, 0.1, " studs") Slider(Dodge, "Dodge easing", "C_dodgeRamp", 0, 0.2, function() return CONFIG.dodgeRamp end, function(Value) CONFIG.dodgeRamp = Value end, 0.001, " s") Slider(Dodge, "Dodge hold", "C_dodgeHold", 0, 1.5, function() return CONFIG.dodgeHold end, function(Value) CONFIG.dodgeHold = Value end, 0.001, " s") Slider(Dodge, "Dodge reacts within", "C_dodgeRange", 10, 200, function() return CONFIG.dodgeRange end, function(Value) CONFIG.dodgeRange = Value end, 1, " studs") Slider(Dodge, "Incoming shot trim", "C_viewDelay", 0, 0.2, function() return CONFIG.viewDelay end, function(Value) CONFIG.viewDelay = Value end, 0.001, " s") Toggle(Dodge, "Face whoever we dodge", "C_dodgeFacing", function() return CONFIG.dodgeFacing end, function(Value) CONFIG.dodgeFacing = Value end) Toggle(Dodge, "Steady camera on dodge", "C_dodgeSteadyCamera", function() return CONFIG.dodgeSteadyCamera end, function(Value) CONFIG.dodgeSteadyCamera = Value end) Toggle(Dodge, "Re-step inside the flight", "C_dodgeDouble", function() return CONFIG.dodgeDouble end, function(Value) CONFIG.dodgeDouble = Value end) Toggle(Dodge, "Hop out of melee reach", "C_meleeHop", function() return CONFIG.meleeHop end, function(Value) CONFIG.meleeHop = Value end) Slider(Dodge, "Hop height", "C_meleeHopHeight", 4, 20, function() return CONFIG.meleeHopHeight end, function(Value) CONFIG.meleeHopHeight = Value end, 0.1, " studs") -- -------------------------------- misc: keys, menu, info local Keys = Section(Misc, "Keybinds", 2) Keys:Label("Panic (unload)"):Keybind({ Name = "Panic", Flag = "K_panic", Default = Enum.KeyCode.Delete, Mode = "Hold", Callback = function(State) if State then pcall(function() getgenv().TribesOverlay.unload() end) end end, }) Keys:Label("Show / hide menu"):Keybind({ Name = "Show / hide menu", Flag = "K_window", Default = Enum.KeyCode.RightControl, Mode = "Hold", Callback = function(State) if uiWindow then uiWindow:SetOpen(State) end end, }) Library:CreateSettingsPage(Window, KeybindList, Watermark) -- -------------------------------- status ticker local function RecentRate() local Cal = STATE.cal if not Cal then return 0, 0, 0 end local Hits = Cal.hitCount or 0 local Total = Hits + (Cal.missCount or 0) local Recent = Cal.recent or {} local Sum = 0 for _, Value in ipairs(Recent) do Sum += Value end local Percent = #Recent > 0 and math.floor(Sum / #Recent * 100) or 0 return Hits, Total, Percent end local Accum = 0 RunService.RenderStepped:Connect(function(Delta) if closed then return end Accum += Delta if Accum < 0.25 then return end Accum = 0 pcall(function() updateWallbang() Watermark:SetVisibility(VIS.watermark) if VIS.watermark then Watermark:SetText(string.format("trident aim %s fov %d %s pool %d", CONFIG.enabled and "on" or "off", math.floor(CONFIG.fov + 0.5), CONFIG.targets, STATE.pool or 0)) end TargetHud:SetVisibility(VIS.targetHud) local Target = HudTarget() local TargetPlayer = Target and Players:GetPlayerFromCharacter(Target) or nil if TargetPlayer then if STATE.hudPlayer ~= TargetPlayer then STATE.hudPlayer = TargetPlayer TargetHud:SetPlayer(TargetPlayer) end if HudAvatar then HudAvatar.Visible = true end else STATE.hudPlayer = nil if HudName then local TheirRoot = Target and Target:FindFirstChild("HumanoidRootPart") local MyRoot = LP.Character and LP.Character:FindFirstChild("HumanoidRootPart") local Gap = TheirRoot and MyRoot and math.floor((TheirRoot.Position - MyRoot.Position).Magnitude + 0.5) or 0 HudName.Text = Target and string.format("%s (%d studs)", Target.Name, Gap) or "no target" end if HudAvatar then HudAvatar.Visible = false end end local Health = 0 if Target then local Humanoid = Target:FindFirstChildOfClass("Humanoid") if Humanoid and Humanoid.MaxHealth > 0 then Health = math.clamp(Humanoid.Health / Humanoid.MaxHealth * 100, 0, 100) end end HealthBar:SetPercentage(Health) local Hits, Total, Percent = RecentRate() HitBar:SetPercentage(math.clamp(Percent, 0, 100)) local Locked = Target and STATE.lock and STATE.lock.model == Target and STATE.lock.solution TargetLine:SetText(Target and string.format("target %s %.0f studs %.1f deg off centre", Target.Name, Locked and (Locked.distance or 0) or 0, Locked and (Locked.angle or 0) or 0) or "target -") AimLine:SetText(string.format("aim %s shots %d hits %d/%d", CONFIG.enabled and "on" or "off", STATE.redirected or 0, STATE.ourHits or 0, STATE.hitEvents or 0)) PoolLine:SetText(string.format("pool %d mode %s tracers %d marks %d", STATE.pool or 0, CONFIG.mode, STATE.tracersDrawn or 0, STATE.markersDrawn or 0)) local solve = STATE.lastSolve LeadLine:SetText(string.format("fov %.0f deg flight %.3fs lead %.2fx ping %.3fs %s", CONFIG.fov, (solve and solve.flight) or 0, CONFIG.leadScale or 1, oneWayLatency(), CONFIG.predict and "solved" or "predict off")) HitLine:SetText(string.format("hit rate %d/%d recent %d%%", Hits, Total, Percent)) CalLine:SetText(string.format("gated %d shots at us %d last hit %s", (STATE.cal and STATE.cal.gated) or 0, STATE.shotsAtUs or 0, STATE.lastHit and tostring(STATE.lastHit.amount) or "-")) EnvLine:SetText(string.format("%s place %d esp %s", game.Name, game.PlaceId, VIS.enabled and "on" or "off")) ErrLine:SetText(STATE.lastError and ("error: " .. tostring(STATE.lastError)) or (STATE.espError and ("esp: " .. tostring(STATE.espError):sub(1, 70)) or "no errors")) end) end) uiWindow = Window uiRefresh() end -- ------------------------------------------------------------------ ESP (screen-space instances) -- Delta exposes no working Drawing API, so the whole overlay is pooled GuiObjects. -- Bounding boxes come from the mesh bounding box, edges are rotated Frames, and -- off-screen targets are hidden rather than rebuilt so the cost stays flat. local SKEL_R6 = { { "Head", "Torso" }, { "Torso", "Left Arm" }, { "Torso", "Right Arm" }, { "Torso", "Left Leg" }, { "Torso", "Right Leg" }, } local SKEL_R15 = { { "Head", "UpperTorso" }, { "UpperTorso", "LowerTorso" }, { "UpperTorso", "LeftUpperArm" }, { "LeftUpperArm", "LeftLowerArm" }, { "LeftLowerArm", "LeftHand" }, { "UpperTorso", "RightUpperArm" }, { "RightUpperArm", "RightLowerArm" }, { "RightLowerArm", "RightHand" }, { "LowerTorso", "LeftUpperLeg" }, { "LeftUpperLeg", "LeftLowerLeg" }, { "LeftLowerLeg", "LeftFoot" }, { "LowerTorso", "RightUpperLeg" }, { "RightUpperLeg", "RightLowerLeg" }, { "RightLowerLeg", "RightFoot" }, } local GuiService = game:GetService("GuiService") local espGui = mk("ScreenGui", { Name = "TribesESP", IgnoreGuiInset = true, ResetOnSpawn = false, DisplayOrder = 2147483000, ZIndexBehavior = Enum.ZIndexBehavior.Sibling, }, container()) local ESP = { pool = setmetatable({}, { __mode = "k" }), gui = espGui } -- ------------------------------------------------------------------ wallbang indicator -- One dot and at most two words, just under the crosshair. It says only what you cannot see -- for yourself: whether a shot from here has a launch point past the cover, and whether the -- last shot actually used one. A clear line needs no badge, so it shows nothing at all. local wbHolder = mk("Frame", { Name = "WallbangIndicator", Parent = espGui, BackgroundTransparency = 1, AnchorPoint = Vector2.new(0.5, 0), Position = UDim2.new(0.5, 0, 0.5, 26), Size = UDim2.new(0, 150, 0, 14), Visible = false, ZIndex = 60, }, espGui) local wbText = mk("TextLabel", { Parent = wbHolder, BackgroundTransparency = 1, Font = Enum.Font.GothamMedium, TextSize = 11, Text = "", TextColor3 = Color3.fromRGB(200, 200, 200), TextStrokeTransparency = 0.7, TextStrokeColor3 = Color3.new(0, 0, 0), TextXAlignment = Enum.TextXAlignment.Center, TextYAlignment = Enum.TextYAlignment.Center, Size = UDim2.new(1, 0, 1, 0), ZIndex = 61, }, wbHolder) local WB = { path = Color3.fromRGB(245, 190, 60), engaged = Color3.fromRGB(80, 235, 140), blocked = Color3.fromRGB(240, 80, 80), } STATE.wbEngagedAt = 0 STATE.wbEngagedDist = nil local lastWb = 0 local wbState = "none" local wbPath = nil updateWallbang = function() local now = os.clock() if not VIS.wallbangIndicator then wbHolder.Visible = false return end -- did a wallbang shot just go out? that is the one thing worth flashing if now - (STATE.loadedAt or 0) < 4 and (STATE.wbEngagedAt or 0) == 0 then wbText.TextColor3 = WB.path wbText.Text = "wallbang ready" wbHolder.Visible = true return end if now - (STATE.wbEngagedAt or 0) < 0.6 then wbText.TextColor3 = WB.engaged wbText.Text = string.format("engaged %.1f", STATE.wbEngagedDist or 0) wbHolder.Visible = true return end if now - lastWb > 0.2 then lastWb = now local ok, state, path = pcall(function() local char = LP.Character local tool = char and char:FindFirstChildWhichIsA("Tool") local origin = tool and aimOrigin(tool) if not origin then local root = char and char:FindFirstChild("HumanoidRootPart") if not root then return "none", nil end origin = root.Position + Vector3.new(0, 2, 0) end local target = selectTarget(origin, (lock and lock.model and lock.model.Parent) and lock.model or nil) if not target then return "none", nil end if hasLine(origin, target.part.Position, target.model) then return "clear", nil end if not CONFIG.wallbang then return "blocked", nil end local candidate, moved = findWallbangOrigin(origin, target.part.Position, target.model, wallbangReach()) if candidate then return "path", moved end return "blocked", nil end) if not ok then state, path = "none", nil end wbState, wbPath = state, path end if wbState == "path" then wbText.TextColor3 = WB.path wbText.Text = string.format("path %.1f", wbPath or 0) wbHolder.Visible = true elseif wbState == "blocked" then wbText.TextColor3 = WB.blocked wbText.Text = "blocked" wbHolder.Visible = true else wbHolder.Visible = false end end -- Cham support. The patterns are the reference set, one value in 0..1 per part that -- multiplies the colour, so the same code drives every mode. The part pass mirrors -- what the reference does: skip the root and accessory handles, keep the original -- material/colour/transparency so it can be put back exactly. ESP.patterns = { "Static", "Wave", "Pulse", "Noise", "Lightning", "Scanline", "Grid" } ESP.materials = { "ForceField", "Neon", "Glass", "Plastic", "SmoothPlastic", "Metal", "Foil", "Ice", "Marble", "Granite", "Slate", "Concrete", "Wood", "Fabric" } ESP.pattern = function(part, mode, speed) local t = os.clock() * (speed or 3) if mode == "Wave" then return (math.sin(t + part.Position.X * 0.2 + part.Position.Y * 0.2) + 1) * 0.5 elseif mode == "Pulse" then return (math.sin(t * 2) + 1) * 0.5 elseif mode == "Noise" then return (math.noise(part.Position.X * 0.1, part.Position.Y * 0.1, t * 0.5) + 1) * 0.5 elseif mode == "Lightning" then return math.abs(math.sin(t * 12 + part.Position.X)) * 0.8 + 0.2 elseif mode == "Scanline" then return math.clamp(math.sin(t + part.Position.Y * 0.5) * 1.2, 0, 1) elseif mode == "Grid" then return (math.abs(math.sin(part.Position.X * 0.3 + t * 1.5)) + math.abs(math.sin(part.Position.Y * 0.3 + t * 1.5))) * 0.5 end return 1 end ESP.shouldCham = function(part) if not part:IsA("BasePart") then return false end if part.Name == "HumanoidRootPart" then return false end local parent = part.Parent if parent and parent:IsA("Accessory") then return false end -- invisible volumes such as the damage hull are not worth recolouring if part.Transparency >= 1 then return false end return true end ESP.applyMaterial = function(model, store, color, material, pattern, alpha, speed) for _, part in ipairs(model:GetDescendants()) do if ESP.shouldCham(part) then if not store[part] then store[part] = { Material = part.Material, Color = part.Color, Transparency = part.Transparency } end local value = ESP.pattern(part, pattern, speed) pcall(function() if Enum.Material[material] then part.Material = Enum.Material[material] end part.Color = Color3.new(color.R * value, color.G * value, color.B * value) part.Transparency = alpha end) end end end -- Tracers: a thin neon part stretched along the flight path, faded out and recycled. -- Pooled because a fight can produce a lot of them and the phone should not be -- creating and destroying parts per shot. ESP.tracerPool = {} ESP.tracerIndex = 0 ESP.spawnTracer = function(from, to, color, life, thickness) if typeof(from) ~= "Vector3" or typeof(to) ~= "Vector3" then STATE.tracerBadArgs = (STATE.tracerBadArgs or 0) + 1 STATE.lastBadArgs = { from = typeof(from), to = typeof(to) } return end local delta = to - from local length = delta.Magnitude if length < 1 then STATE.tracerTooShort = (STATE.tracerTooShort or 0) + 1 STATE.lastShort = { from = from, to = to, length = length } return end ESP.tracerIndex = (ESP.tracerIndex % 32) + 1 local part = ESP.tracerPool[ESP.tracerIndex] if not part then part = Instance.new("Part") part.Anchored = true part.CanCollide = false part.CanQuery = false part.CanTouch = false part.CastShadow = false part.Material = Enum.Material.Neon part.Name = "Tracer" part.Parent = workspace ESP.tracerPool[ESP.tracerIndex] = part end local size = thickness or 0.07 -- start just past the muzzle so it does not draw out of our own chest, and -- measure the span from there rather than keeping the untrimmed length local start = from + delta.Unit * 1.2 local span = to - start part.Size = Vector3.new(size, size, span.Magnitude) part.CFrame = CFrame.lookAt(start + span * 0.5, to) part.Color = color or Color3.fromRGB(120, 200, 255) part.Transparency = 0.05 TweenService:Create(part, TweenInfo.new(life or 0.22), { Transparency = 1 }):Play() STATE.tracersDrawn = (STATE.tracersDrawn or 0) + 1 STATE.lastTracer = { from = from, to = to, at = os.clock() } logEvent("fx", string.format("tracer %.0f studs from %s to %s", span.Magnitude, string.format("%.0f,%.0f,%.0f", from.X, from.Y, from.Z), string.format("%.0f,%.0f,%.0f", to.X, to.Y, to.Z))) end -- Impact markers: a billboard cross pinned at the impact, on a pooled invisible part ESP.markerPool = {} ESP.markerIndex = 0 ESP.spawnMarker = function(position, color, size, life) if typeof(position) ~= "Vector3" then return end ESP.markerIndex = (ESP.markerIndex % 24) + 1 local entry = ESP.markerPool[ESP.markerIndex] if not entry then local part = Instance.new("Part") part.Anchored = true part.CanCollide = false part.CanQuery = false part.CanTouch = false part.CastShadow = false part.Transparency = 1 part.Size = Vector3.new(0.2, 0.2, 0.2) part.Name = "ImpactMark" part.Parent = workspace local gui = Instance.new("BillboardGui") gui.Name = "Mark" gui.AlwaysOnTop = true gui.LightInfluence = 0 gui.Adornee = part gui.Size = UDim2.fromOffset(22, 22) gui.Parent = part local arms = {} for i = 1, 2 do local arm = Instance.new("Frame") arm.BackgroundColor3 = Color3.new(1, 1, 1) arm.BorderSizePixel = 0 arm.AnchorPoint = Vector2.new(0.5, 0.5) arm.Position = UDim2.fromScale(0.5, 0.5) arm.Size = UDim2.new(0, 2, 0.82, 0) arm.Rotation = i == 1 and 45 or -45 arm.Parent = gui arms[i] = arm end entry = { part = part, gui = gui, arms = arms } ESP.markerPool[ESP.markerIndex] = entry end local size = size or 22 local life = life or 0.35 entry.part.Position = position entry.gui.Size = UDim2.fromOffset(size, size) for _, arm in ipairs(entry.arms) do arm.BackgroundColor3 = color or Color3.new(1, 1, 1) arm.BackgroundTransparency = 0 end TweenService:Create(entry.gui, TweenInfo.new(life), { Size = UDim2.fromOffset(size * 0.6, size * 0.6), }):Play() for _, arm in ipairs(entry.arms) do TweenService:Create(arm, TweenInfo.new(life), { BackgroundTransparency = 1 }):Play() end STATE.markersDrawn = (STATE.markersDrawn or 0) + 1 end -- The CombatFX handler is defined far above this block, so it cannot name the ESP -- table: up there a local that does not exist yet resolves to the global nil, and -- `pcall(ESP.spawnTracer, ...)` throws while evaluating its own arguments, which -- silently aborts the whole handler before it draws anything. These references are -- published on STATE, which does exist that early, and the handler uses them. STATE.fxTracer = ESP.spawnTracer STATE.fxMarker = ESP.spawnMarker ESP.restoreMaterial = function(store) for part, data in pairs(store) do if part and part.Parent then pcall(function() part.Material = data.Material part.Color = data.Color part.Transparency = data.Transparency end) end store[part] = nil end end STATE.screenInset = function() local ok, v = pcall(function() return GuiService:GetGuiInset() end) if ok and typeof(v) == "Vector2" then return v end return Vector2.new(0, 0) end -- ScreenGui space here is the viewport, so the world -> UI mapping is -- WorldToViewportPoint. WorldToScreenPoint on Delta returns viewport minus the -- inset (measured 58px on this device) and draws the whole overlay too high. local function screenCenter() local vs = Camera.ViewportSize return Vector2.new(vs.X * 0.5, vs.Y * 0.5) end local function project(v3) local sp, onScreen = Camera:WorldToViewportPoint(v3) if onScreen and sp.Z > 0 then return Vector2.new(sp.X, sp.Y) end return nil end local function projectBox(cf, size) local half = size * 0.5 local minX, minY, maxX, maxY = math.huge, math.huge, -math.huge, -math.huge local any = false for xi = -1, 1, 2 do for yi = -1, 1, 2 do for zi = -1, 1, 2 do local world = (cf * CFrame.new(half.X * xi, half.Y * yi, half.Z * zi)).Position local sp, on = Camera:WorldToViewportPoint(world) if on and sp.Z > 0 then any = true minX = math.min(minX, sp.X); maxX = math.max(maxX, sp.X) minY = math.min(minY, sp.Y); maxY = math.max(maxY, sp.Y) end end end end if not any then return nil end return minX, minY, maxX, maxY end local function espSquare(parent) return mk("Frame", { BorderSizePixel = 0, BackgroundColor3 = Color3.new(1, 1, 1), Visible = false, }, parent) end local function espLine(parent) local f = espSquare(parent) f.AnchorPoint = Vector2.new(0.5, 0.5) return f end local function espText(parent, size) return mk("TextLabel", { BackgroundTransparency = 1, BorderSizePixel = 0, Font = Enum.Font.Code, TextSize = size, TextColor3 = Color3.new(1, 1, 1), TextStrokeTransparency = 0.4, TextStrokeColor3 = Color3.fromRGB(0, 0, 0), TextXAlignment = Enum.TextXAlignment.Center, TextYAlignment = Enum.TextYAlignment.Center, AnchorPoint = Vector2.new(0.5, 0.5), Size = UDim2.fromOffset(280, size + 4), Visible = false, }, parent) end local function buildState() local s = { full = {}, corners = {}, skel = {} } for i = 1, 4 do s.full[i] = espLine(espGui) end for i = 1, 8 do s.corners[i] = espLine(espGui) end s.skel = {} s.fill = espSquare(espGui) s.hbBg = espSquare(espGui) s.hbFill = espSquare(espGui) s.dot = espSquare(espGui) round(s.dot, 4) s.snap = espLine(espGui) s.name = espText(espGui, 13) s.dist = espText(espGui, 12) s.wpn = espText(espGui, 12) s.chams = mk("Highlight", { Name = "Chams", Enabled = false, FillTransparency = 1, OutlineTransparency = 0, DepthMode = Enum.HighlightDepthMode.AlwaysOnTop, }, espGui) s.matStore = {} return s end local function stateFor(model) local s = ESP.pool[model] if not s then s = buildState() ESP.pool[model] = s end return s end local function hideAll(s) for _, f in ipairs(s.full) do f.Visible = false end for _, f in ipairs(s.corners) do f.Visible = false end for _, f in ipairs(s.skel) do f.Visible = false end s.fill.Visible = false s.hbBg.Visible = false s.hbFill.Visible = false s.dot.Visible = false s.snap.Visible = false s.name.Visible = false s.dist.Visible = false s.wpn.Visible = false end local function hideState(model) local s = ESP.pool[model] if s then hideAll(s) -- the 2D parts are per frame, but a highlight and recoloured body persist, -- so they are cleared here, on the path that means "not shown this frame" if s.chams then s.chams.Enabled = false end if s.matStore and next(s.matStore) then ESP.restoreMaterial(s.matStore) end end end local function destroyState(s) if s.chams then s.chams:Destroy() end if s.matStore then ESP.restoreMaterial(s.matStore) end for _, f in ipairs(s.full) do f:Destroy() end for _, f in ipairs(s.corners) do f:Destroy() end for _, f in ipairs(s.skel) do f:Destroy() end for _, f in ipairs({ s.fill, s.hbBg, s.hbFill, s.dot, s.snap, s.name, s.dist, s.wpn }) do f:Destroy() end end -- A rotated Frame centred on the segment midpoint, so only one edge per line. local function setLine(f, a, b, color, thickness) local dx, dy = b.X - a.X, b.Y - a.Y local length = math.sqrt(dx * dx + dy * dy) if length < 0.5 then f.Visible = false return end f.Position = UDim2.fromOffset((a.X + b.X) * 0.5, (a.Y + b.Y) * 0.5) f.Size = UDim2.fromOffset(length, thickness or 1) f.Rotation = math.deg(math.atan2(dy, dx)) f.BackgroundColor3 = color f.Visible = true end local function healthColor(frac) return VIS.healthBad:Lerp(VIS.healthGood, math.clamp(frac, 0, 1)) end -- Angular FOV to viewport pixels: the ring then matches the aim cone exactly. local function fovPixelRadius() local vertical = math.rad(math.clamp(Camera.FieldOfView or 70, 1, 179)) local aim = math.rad(math.clamp(CONFIG.fov, 0, 179)) local half = math.tan(aim * 0.5) / math.tan(vertical * 0.5) return half * (Camera.ViewportSize.Y * 0.5) end local function renderSkeleton(s, char, color) local joints = char:FindFirstChild("UpperTorso") and SKEL_R15 or SKEL_R6 local idx = 0 for _, pair in ipairs(joints) do local a = char:FindFirstChild(pair[1]) local b = char:FindFirstChild(pair[2]) if a and b and a:IsA("BasePart") and b:IsA("BasePart") then local pa, pb = project(a.Position), project(b.Position) if pa and pb then idx += 1 local f = s.skel[idx] if not f then f = espLine(espGui) s.skel[idx] = f end setLine(f, pa, pb, color, 1) end end end return idx end local fovRing = mk("Frame", { BackgroundTransparency = 1, BorderSizePixel = 0, AnchorPoint = Vector2.new(0.5, 0.5), Size = UDim2.fromOffset(2, 2), Visible = false, }, espGui) local ringCorner = Instance.new("UICorner") ringCorner.CornerRadius = UDim.new(1, 0) ringCorner.Parent = fovRing local ringStroke = Instance.new("UIStroke") ringStroke.Thickness = 1.5 ringStroke.Color = Color3.fromRGB(255, 255, 255) ringStroke.Transparency = 0.35 ringStroke.Parent = fovRing local lastShown = {} local cleanupAccum = 0 ESP.updateRealBody = function() local root = LP.Character and LP.Character:FindFirstChild("HumanoidRootPart") local rb = ESP.realBody if not CONFIG.realBody or closed or not root then if rb then rb.box.Transparency = 1 if rb.shell then rb.shell.Transparency = 1 end rb.tile.Transparency = 1 rb.beam.Transparency = NumberSequence.new(1) rb.gui.Enabled = false rb.ring.Visible = false rb.tag.Visible = false end return end if not rb then rb = {} local function anchor(name) local part = Instance.new("Part") part.Name = name part.Anchored = true part.CanCollide = false part.CanQuery = false part.CanTouch = false part.CastShadow = false part.Transparency = 1 part.Size = Vector3.new(0.2, 0.2, 0.2) part.Parent = workspace local attach = Instance.new("Attachment") attach.Parent = part return part, attach end rb.real, rb.realAtt = anchor("RealBodyAnchor") rb.shown, rb.shownAtt = anchor("ShownBodyAnchor") -- the body, as a wireframe box: crisp, always on top, and with no volume to it local box = Instance.new("BoxHandleAdornment") box.Name = "RealBodyBox" box.Adornee = rb.real box.AlwaysOnTop = true box.ZIndex = 4 box.Size = Vector3.new(2.5, 5.3, 1.3) box.Transparency = 0.55 box.Color3 = Color3.fromRGB(120, 220, 255) box.Parent = rb.real rb.box = box -- footprint on the floor: anchors the marker to the ground under the real body local tile = Instance.new("Part") tile.Name = "RealBodyFootprint" tile.Anchored = true tile.CanCollide = false tile.CanQuery = false tile.CanTouch = false tile.CastShadow = false tile.Material = Enum.Material.Neon tile.Color = Color3.fromRGB(120, 220, 255) tile.Transparency = 0.7 tile.Size = Vector3.new(3, 0.06, 3) tile.Parent = workspace rb.tile = tile -- tether: a beam, so it stays a hairline at any distance and cannot be tripped over local beam = Instance.new("Beam") beam.Name = "RealBodyTether" beam.Attachment0 = rb.realAtt beam.Attachment1 = rb.shownAtt beam.Width0, beam.Width1 = 0.075, 0.075 beam.FaceCamera = true beam.LightEmission = 0.55 beam.Segments = 1 beam.Color = ColorSequence.new(Color3.fromRGB(120, 220, 255)) beam.Transparency = NumberSequence.new(0.25) beam.Parent = rb.real rb.beam = beam local gui = Instance.new("BillboardGui") gui.Name = "RealBodyTag" gui.AlwaysOnTop = true gui.LightInfluence = 0 gui.Adornee = rb.real gui.Size = UDim2.fromOffset(120, 16) gui.StudsOffsetWorldSpace = Vector3.new(0, 3.3, 0) gui.Parent = rb.real local text = Instance.new("TextLabel") text.BackgroundTransparency = 1 text.Font = Enum.Font.SourceSansBold text.TextSize = 12 text.TextColor3 = Color3.fromRGB(120, 220, 255) text.TextStrokeTransparency = 0.55 text.TextStrokeColor3 = Color3.fromRGB(0, 0, 0) text.Text = "" text.Size = UDim2.fromScale(1, 1) text.Parent = gui rb.gui, rb.text = gui, text -- chevron orbiting the crosshair: direction to the real body without looking away local ring = mk("Frame", { Name = "RealBodyChevron", BackgroundTransparency = 1, AnchorPoint = Vector2.new(0.5, 0.5), Visible = false, }, espGui) local function arm(anchorPoint, rotation) return mk("Frame", { BackgroundColor3 = Color3.fromRGB(120, 220, 255), BorderSizePixel = 0, AnchorPoint = anchorPoint, Position = UDim2.fromScale(0.5, 0), Size = UDim2.fromOffset(1.6, 11), Rotation = rotation, }, ring) end rb.armA, rb.armB = arm(Vector2.new(1, 1), -32), arm(Vector2.new(0, 1), 32) rb.tag = mk("TextLabel", { Name = "RealBodyDistance", BackgroundTransparency = 1, Font = Enum.Font.SourceSansBold, TextSize = 13, TextColor3 = Color3.fromRGB(120, 220, 255), TextStrokeTransparency = 0.55, TextStrokeColor3 = Color3.fromRGB(0, 0, 0), Text = "", AnchorPoint = Vector2.new(0.5, 0.5), Size = UDim2.fromOffset(90, 16), Visible = false, }, espGui) rb.ring = ring ESP.realBody = rb end local offset = STATE.appliedOffset or Vector3.new(0, 0, 0) local shown = offset.Magnitude -- Only while there is something to show. At rest the replicated body and the body on -- screen are the same position, so the marker would be drawn inside the fake body - -- an outline around nothing, exactly where the body already is. What the marker is for -- is saying "this is where you really are", which only means anything once that is -- somewhere else. So it appears on the flick and disappears when the body settles. local flicked = rb.last and (offset - rb.last).Magnitude > 1 rb.last = offset if shown <= 0.75 then rb.box.Transparency = 1 rb.tile.Transparency = 1 rb.beam.Transparency = NumberSequence.new(1) rb.gui.Enabled = false rb.ring.Visible = false rb.tag.Visible = false if rb.jitAnchor then rb.pip.Transparency = 1 rb.capA.Transparency = 1 rb.capB.Transparency = 1 rb.jitGui.Enabled = false end return end -- Jitter mode shows the envelope rather than the position. A shake changes the body's -- place every few milliseconds, so a marker that follows it thrashes and says nothing - -- there is no "where you are" to draw. What is worth seeing is the reach: a band for the -- ground the body is covering, end caps for its extremes, and one smoothed pip showing -- roughly where in that band it currently is. Nothing here moves at the shake's rate. local jittering = CONFIG.jitterDodge and STATE.jitterUntil and os.clock() < STATE.jitterUntil if jittering and STATE.jitterAcross then if not rb.jitAnchor then -- no line on the ground: the caps and the pip already say where the shake reaches -- and roughly where the body is, and a bar across the floor just draws a stripe -- over the map. This is an invisible anchor for the label, nothing else. local anchor = Instance.new("Part") anchor.Name = "JitterAnchor" anchor.Anchored = true anchor.CanCollide = false anchor.CanQuery = false anchor.CanTouch = false anchor.CastShadow = false anchor.Transparency = 1 anchor.Size = Vector3.new(0.2, 0.2, 0.2) anchor.Parent = workspace local pip = Instance.new("Part") pip.Name = "JitterPip" pip.Shape = Enum.PartType.Ball pip.Anchored = true pip.CanCollide = false pip.CanQuery = false pip.CanTouch = false pip.CastShadow = false pip.Material = Enum.Material.Neon pip.Size = Vector3.new(0.9, 0.9, 0.9) pip.Parent = workspace local function cap() local c = Instance.new("Part") c.Anchored = true c.CanCollide = false c.CanQuery = false c.CanTouch = false c.CastShadow = false c.Material = Enum.Material.Neon c.Size = Vector3.new(0.3, 0.3, 0.3) c.Parent = workspace return c end local gui = Instance.new("BillboardGui") gui.Name = "JitterTag" gui.AlwaysOnTop = true gui.LightInfluence = 0 gui.Adornee = anchor gui.Size = UDim2.fromOffset(120, 16) gui.StudsOffsetWorldSpace = Vector3.new(0, 2.2, 0) gui.Parent = anchor local text = Instance.new("TextLabel") text.BackgroundTransparency = 1 text.Font = Enum.Font.SourceSansBold text.TextSize = 12 text.TextStrokeTransparency = 0.55 text.TextStrokeColor3 = Color3.fromRGB(0, 0, 0) text.Size = UDim2.fromScale(1, 1) text.Parent = gui rb.jitAnchor, rb.pip, rb.capA, rb.capB, rb.jitGui, rb.jitText = anchor, pip, cap(), cap(), gui, text end -- the marker proper stands down: none of it can render a position that changes faster -- than the frame that draws it rb.box.Transparency = 1 rb.tile.Transparency = 1 rb.beam.Transparency = NumberSequence.new(1) rb.gui.Enabled = false rb.ring.Visible = false rb.tag.Visible = false local real = root.Position local base = real - offset local axis = STATE.jitterAcross local low = math.min(CONFIG.jitterMin or 3, CONFIG.jitterMax or 15) local reach = math.max(STATE.jitterReach or 15, low) local colour = VIS.realBodyColor or Color3.fromRGB(120, 220, 255) rb.jitAnchor.CFrame = CFrame.new(base) for _, cap in ipairs({ rb.capA, rb.capB }) do cap.Color = colour cap.Transparency = 0.2 end rb.capA.CFrame = CFrame.new(base + axis * reach) rb.capB.CFrame = CFrame.new(base - axis * reach) -- the pip follows the body, eased, so it reads as sliding inside the band rather than -- flickering between its ends rb.pipPos = rb.pipPos and rb.pipPos:Lerp(real, 0.22) or real rb.pip.CFrame = CFrame.new(rb.pipPos) rb.pip.Color = colour rb.pip.Transparency = 0.25 rb.jitGui.Enabled = true rb.jitText.Text = string.format("SHAKING %.0f-%.0f studs", low, reach) rb.jitText.TextColor3 = colour return end if rb.jitAnchor and rb.jitGui.Enabled then rb.pip.Transparency = 1 rb.capA.Transparency = 1 rb.capB.Transparency = 1 rb.jitGui.Enabled = false rb.pipPos = nil end if flicked then rb.flashAt = os.clock() end -- colour says how far off the body is: cyan for a step that barely matters, amber for -- one big enough to decide a shot on its own local heat = math.clamp(shown / 9, 0, 1) local base = VIS.realBodyColor or Color3.fromRGB(120, 220, 255) local color = CONFIG.realBodyHeat and base:Lerp(Color3.fromRGB(255, 140, 70), heat) or base local scale = CONFIG.realBodyScale or 1 local real = root.Position local visible = real - offset local flash = rb.flashAt and (1 - math.clamp((os.clock() - rb.flashAt) / 0.28, 0, 1)) or 0 rb.real.CFrame = root.CFrame rb.shown.CFrame = CFrame.new(visible) rb.box.Color3 = color rb.box.Size = Vector3.new(2.5, 5.3, 1.3) * scale rb.box.Transparency = 0.55 - 0.3 * heat - 0.2 * flash if rb.shell then rb.shell.Size = Vector3.new(2.5, 5.3, 1.3) * scale end rb.text.TextColor3 = color rb.text.Text = string.format("OFF %.1f", shown) rb.gui.Enabled = CONFIG.realBodyTag ~= false -- footprint: under the real body, snapped to whatever is beneath it local under = workspace:Raycast(real, Vector3.new(0, -8, 0)) local floorY = under and under.Position.Y or (real.Y - 2.9) rb.tile.Position = Vector3.new(real.X, floorY + 0.04, real.Z) rb.tile.Color = color rb.tile.Transparency = 0.7 - 0.35 * flash local spread = (3 + 1.1 * flash) * scale rb.tile.Size = Vector3.new(spread, 0.06, spread) rb.beam.Color = ColorSequence.new(color) rb.beam.Transparency = NumberSequence.new(0.3 - 0.2 * flash) -- the chevron: angle to the real body, from the projection when it is on screen and -- from the camera's own axes when it is not, so it stays right even behind us local centre = screenCenter() local dx, dy local sp = project(real) if sp then dx, dy = sp.X - centre.X, sp.Y - centre.Y else local rel = real - Camera.CFrame.Position dx, dy = rel:Dot(Camera.CFrame.RightVector), -rel:Dot(Camera.CFrame.UpVector) end if (dx * dx + dy * dy) < 4 or CONFIG.realBodyChevron == false then rb.ring.Visible = false rb.tag.Visible = false else local angle = math.deg(math.atan2(dy, dx)) + 90 local radius = math.min(Camera.ViewportSize.X, Camera.ViewportSize.Y) * 0.17 rb.ring.Size = UDim2.fromOffset(radius * 2, radius * 2) rb.ring.Position = UDim2.fromOffset(centre.X, centre.Y) rb.ring.Rotation = angle rb.ring.Visible = true for _, part in ipairs({ rb.armA, rb.armB }) do part.BackgroundColor3 = color end local len = math.sqrt(dx * dx + dy * dy) rb.tag.Position = UDim2.fromOffset(centre.X + (dx / len) * (radius + 17), centre.Y + (dy / len) * (radius + 17)) rb.tag.Text = string.format("%.1f", shown) rb.tag.TextColor3 = color rb.tag.Visible = CONFIG.realBodyTag ~= false end end local function ESPRender(dt) Camera = workspace.CurrentCamera or Camera local vs = Camera.ViewportSize local center = screenCenter() -- independent of the ESP toggle: this is about the anti-aim, not the overlay's boxes if ESP.updateRealBody then ESP.updateRealBody() end if VIS.fovRing and VIS.enabled and CONFIG.enabled then local radius = math.max(1, fovPixelRadius()) fovRing.Visible = true fovRing.Position = UDim2.fromOffset(center.X, center.Y) fovRing.Size = UDim2.fromOffset(radius * 2, radius * 2) else fovRing.Visible = false end if not VIS.enabled then for model in pairs(lastShown) do hideState(model) end table.clear(lastShown) return end local camPos = Camera.CFrame.Position local seen = {} local active = (lock.model and lock.solution and lock.model.Parent) and lock.model or (STATE.lastSolution and STATE.lastSolution.model) for _, model in ipairs(targetPool()) do local head, root, player = validTarget(model) if head then local dist = (root.Position - camPos).Magnitude if dist <= VIS.maxDistance then local s = stateFor(model) -- highlight chams: colours, alpha, thermal breathing, -- through-wall depth and a distance fade, as in the source if s.chams then if VIS.chams then local breathe = 1 if VIS.chamsThermal then breathe = (math.atan(math.sin(os.clock() * 2)) * 2 / math.pi + 1) * 0.5 breathe = 0.35 + 0.65 * breathe end local fade = 1 if VIS.chamsFade then fade = math.clamp(1 - dist / math.max(VIS.maxDistance, 1), 0.2, 1) end -- bots are people, so they follow the player -- outline; dummies have their own local fillColor = VIS.chamsFill local outlineColor = isDummy(model) and VIS.chamsOutlineDummy or VIS.chamsOutline if VIS.chamsRainbow then local hue = (os.clock() % 5) / 5 fillColor = Color3.fromHSV(hue, 1, 1) outlineColor = Color3.fromHSV((hue + 0.5) % 1, 1, 1) end s.chams.Adornee = model s.chams.FillColor = fillColor s.chams.OutlineColor = outlineColor s.chams.FillTransparency = math.clamp(1 - (VIS.chamsFillAlpha / 100) * breathe * fade, 0, 1) s.chams.OutlineTransparency = math.clamp(1 - (VIS.chamsOutlineAlpha / 100) * breathe * fade, 0, 1) s.chams.DepthMode = VIS.chamsThrough and Enum.HighlightDepthMode.AlwaysOnTop or Enum.HighlightDepthMode.Occluded s.chams.Enabled = true else s.chams.Enabled = false end end -- material chams: the actual body parts recoloured, with the -- pattern animation and a rainbow option from the source if VIS.matChams then local matColor = VIS.matChamsColor if VIS.matChamsRainbow then matColor = Color3.fromHSV((os.clock() % 5) / 5, 1, 1) end ESP.applyMaterial(model, s.matStore, matColor, VIS.matChamsMaterial, VIS.matChamsPattern, VIS.matChamsAlpha, VIS.matChamsSpeed) elseif s.matStore and next(s.matStore) then ESP.restoreMaterial(s.matStore) end local ok, cf, size = pcall(model.GetBoundingBox, model) if ok then local minX, minY, maxX, maxY = projectBox(cf, size) if minX then minX, minY, maxX, maxY = minX - 1, minY - 1, maxX + 1, maxY + 1 local w, h = maxX - minX, maxY - minY if w >= 2 and h >= 2 then seen[model] = true hideAll(s) local color, thick if model == active then color, thick = Color3.fromRGB(255, 220, 120), 2 elseif isDummy(model) then color = VIS.dummyColor thick = 1 else -- no Player instance does not make it a dummy: bots and -- anyone mid-spawn are people and get the enemy colour color = isTeammate(model) and VIS.teamColor or VIS.enemyColor thick = 1 end if VIS.box then if VIS.boxStyle == "Full" then setLine(s.full[1], Vector2.new(minX, minY), Vector2.new(maxX, minY), color, thick) setLine(s.full[2], Vector2.new(minX, maxY), Vector2.new(maxX, maxY), color, thick) setLine(s.full[3], Vector2.new(minX, minY), Vector2.new(minX, maxY), color, thick) setLine(s.full[4], Vector2.new(maxX, minY), Vector2.new(maxX, maxY), color, thick) else local len = math.min(w, h) * 0.25 setLine(s.corners[1], Vector2.new(minX, minY), Vector2.new(minX + len, minY), color, thick) setLine(s.corners[2], Vector2.new(minX, minY), Vector2.new(minX, minY + len), color, thick) setLine(s.corners[3], Vector2.new(maxX - len, minY), Vector2.new(maxX, minY), color, thick) setLine(s.corners[4], Vector2.new(maxX, minY), Vector2.new(maxX, minY + len), color, thick) setLine(s.corners[5], Vector2.new(maxX - len, maxY), Vector2.new(maxX, maxY), color, thick) setLine(s.corners[6], Vector2.new(maxX, maxY - len), Vector2.new(maxX, maxY), color, thick) setLine(s.corners[7], Vector2.new(minX, maxY), Vector2.new(minX + len, maxY), color, thick) setLine(s.corners[8], Vector2.new(minX, maxY - len), Vector2.new(minX, maxY), color, thick) end end if VIS.filledBox then s.fill.Visible = true s.fill.Position = UDim2.fromOffset(minX, minY) s.fill.Size = UDim2.fromOffset(w, h) s.fill.BackgroundColor3 = color s.fill.BackgroundTransparency = VIS.fillAlpha end if VIS.healthBar then local humanoid = model:FindFirstChildOfClass("Humanoid") local frac = humanoid and math.clamp(humanoid.Health / math.max(humanoid.MaxHealth, 1), 0, 1) or 1 s.hbBg.Visible = true s.hbBg.Position = UDim2.fromOffset(minX - 7, minY - 1) s.hbBg.Size = UDim2.fromOffset(5, h + 2) s.hbBg.BackgroundColor3 = Color3.fromRGB(0, 0, 0) s.hbBg.BackgroundTransparency = 0.55 local fh = h * frac s.hbFill.Visible = true s.hbFill.Position = UDim2.fromOffset(minX - 6, minY + (h - fh)) s.hbFill.Size = UDim2.fromOffset(3, math.max(fh, 0.5)) s.hbFill.BackgroundColor3 = healthColor(frac) s.hbFill.BackgroundTransparency = 0 end if VIS.headDot then local hp = project(head.Position) if hp then s.dot.Visible = true s.dot.Position = UDim2.fromOffset(hp.X, hp.Y) s.dot.Size = UDim2.fromOffset(6, 6) s.dot.AnchorPoint = Vector2.new(0.5, 0.5) s.dot.BackgroundColor3 = color s.dot.BackgroundTransparency = 0 end end if VIS.skeleton then renderSkeleton(s, model, color) end if VIS.snapline then setLine(s.snap, Vector2.new(center.X, vs.Y), Vector2.new(minX + w * 0.5, maxY), color, 1) end local cx = minX + w * 0.5 local ts = VIS.textSize if VIS.name then s.name.Visible = true local label if isDummy(model) then label = model.Name elseif player then label = player.Name else label = model:GetAttribute("DisplayName") or model.Name end s.name.Text = label s.name.TextSize = ts s.name.Position = UDim2.fromOffset(cx, minY - ts - 3) s.name.TextColor3 = color end local below = maxY + ts * 0.5 + 4 if VIS.distance then s.dist.Visible = true s.dist.Text = string.format("%d studs", math.floor(dist + 0.5)) s.dist.TextSize = ts - 1 s.dist.Position = UDim2.fromOffset(cx, below) s.dist.TextColor3 = VIS.textColor below += ts end if VIS.weapon then local tool = model:FindFirstChildWhichIsA("Tool") local tag = player and (player:GetAttribute("TribeTag") or "") or "" local label = tool and tool.Name or (tag ~= "" and tag or nil) if label then s.wpn.Visible = true s.wpn.Text = label s.wpn.TextSize = ts - 1 s.wpn.Position = UDim2.fromOffset(cx, below) s.wpn.TextColor3 = VIS.textColor end end end end end end end end for model in pairs(lastShown) do if not seen[model] then hideState(model) end end lastShown = seen -- Reclaim states whose character is gone. cleanupAccum += dt if cleanupAccum >= 1 then cleanupAccum = 0 for model, s in pairs(ESP.pool) do if model.Parent == nil then destroyState(s) ESP.pool[model] = nil lastShown[model] = nil end end end end -- ------------------------------------------------------------------ instant draw / fire -- Drawn weapons compute their power from Charge() = (now - drawStart)/drawTime, so -- reporting a full charge makes a tap shoot at full power with no wait. Guns are -- gated by their own reload/cooldown state, which is cleared before each Fire. local handlerHooks = {} do local combatFolder = LP.PlayerScripts:FindFirstChild("Combat") local handlers = combatFolder and combatFolder:FindFirstChild("Handlers") if handlers then local function patchCharge(moduleName) local module = handlers:FindFirstChild(moduleName) if not module or not module:IsA("ModuleScript") then return end local ok, table_ = pcall(require, module) if not ok or type(table_) ~= "table" or type(table_.Charge) ~= "function" then return end local original = table_.Charge table_.Charge = function(self, ...) if CONFIG.instantFire then local full = self.drawing or self.winding if full then local ignored = original(self, ...) return 1 end end return original(self, ...) end handlerHooks[#handlerHooks + 1] = { table = table_, key = "Charge", original = original } end patchCharge("Bow") patchCharge("Throw") end end -- ------------------------------------------------------------------ auto fire -- Everything a weapon sends is gated server side: gun magazines refill on the -- server's own reload clock, so shots sent around that clock are simply refused. -- This drives the game's real input dispatcher instead, firing the moment the -- server is ready, which removes all of the idle time a human adds. local Controller = require(LP.PlayerScripts.Combat.Controller) local lastAutoShot = 0 -- ------------------------------------------------------------------ melee kill aura -- Melee swings are built client-side: the handler detects its own targets inside -- the weapon's range and arc, then sends that list along with the direction it -- swung in. Both are therefore ours to correct. Every swing is re-aimed at the -- nearest enemy in range (so the arc contains them no matter where we are looking) -- and the hit list is extended to everyone in range, which is the kill aura. local MELEE_KINDS = { Sword = true, Axe = true, Spear = true, Dagger = true, Club = true, Shield = true } local function meleeTool() local char = LP.Character local tool = char and char:FindFirstChildWhichIsA("Tool") if not tool then return nil end local weaponId = tool:GetAttribute("WeaponId") local definition = weaponId and Weapons.Get(weaponId) local kind = definition and definition.kind if not kind or not MELEE_KINDS[kind] then return nil end return tool, definition.tuning or {}, kind end -- Range and reach follow the game's own detection rules: it accepts anything -- within range + 1.2 studs of the body centre and less than 8 studs above or below. local function killAuraRange(tuning) local configured = tonumber(CONFIG.killAuraRange) or 0 if configured > 0 then return configured end return tuning.range or 8 end -- A small margin inside the accepted range: the list is checked by the server a -- ping later, and a target that steps back out of range takes the swing with it. local function killAuraTargets(range) local char = LP.Character local root = char and char:FindFirstChild("HumanoidRootPart") if not root then return {} end local limit = math.max(1, range + 1.2 - 0.6) local found = {} for _, model in ipairs(targetPool()) do local head, theirRoot = validTarget(model) if head and theirRoot then local delta = theirRoot.Position - root.Position local distance = delta.Magnitude if distance <= limit and math.abs(delta.Y) < 8 then found[#found + 1] = { model = model, root = theirRoot, distance = distance } end end end table.sort(found, function(a, b) return a.distance < b.distance end) return found end -- The server takes the swing's hit list from the payload and checks it against the -- swing direction, so a swing is only ever aimed at one target and lists what that -- aim cone contains. The target steps through everyone in range, so a group all get -- hit over successive swings. Sending this payload directly is what makes the aura -- silent: the game's own handler never runs, so nothing swings on screen. killAuraAim = function(tool, tuning, origin, target) local vector = target.root.Position - origin if vector.Magnitude < 0.01 then return nil end local direction = vector.Unit -- the swing is checked against the direction the body faces, and the body is -- turned through the game's own simulation input, which is why the handler -- calls FaceCamera() before every swing local flat = Vector3.new(direction.X, 0, direction.Z) if flat.Magnitude > 0.01 then flat = flat.Unit local yaw = math.atan2(-flat.X, -flat.Z) pcall(function() Simulation.SetFacing(yaw) end) end local half = math.rad((tonumber(tuning.arc) or 60) * 0.5) local cosine = math.cos(half) local hits = { target.model } local inRange = STATE.killAuraList or {} for _, entry in ipairs(inRange) do if entry.model ~= target.model then local to = entry.root.Position - origin if to.Magnitude > 0.01 and direction:Dot(to.Unit) >= cosine then hits[#hits + 1] = entry.model end end end return { tool = tool, dir = direction, hits = hits } end local function killAuraTick() if closed or not CONFIG.enabled or not CONFIG.killAura then return end local tool, tuning = meleeTool() if not tool then return end local char = LP.Character local humanoid = char and char:FindFirstChildOfClass("Humanoid") if not humanoid or humanoid.Health <= 0 then return end local inRange = killAuraTargets(killAuraRange(tuning)) if #inRange == 0 then return end STATE.killAuraList = inRange -- the weapon's own cooldown, plus a margin: the server refuses swings that -- arrive inside its own window and a refused swing is a wasted target local now = os.clock() if now - (STATE.killAuraLast or 0) < math.max(0.2, (tuning.cooldown or 0.5) + 0.06) then return end STATE.killAuraLast = now STATE.killAuraStep = ((STATE.killAuraStep or 0) % #inRange) + 1 local target = inRange[STATE.killAuraStep] local head = char:FindFirstChild("Head") local origin = head and head.Position or char.HumanoidRootPart.Position local payload = killAuraAim(tool, tuning, origin, target) if not payload then return end STATE.killAuraSwings = (STATE.killAuraSwings or 0) + 1 STATE.killAuraPrimary = target.model.Name pcall(function() WeaponAction:FireServer("Swing", payload) end) end local autoPress = false local drawTool = nil local drawUntil = 0 local function releasePrimary() if not autoPress then return end autoPress = false pcall(function() Controller.Dispatch("Primary", false) end) end local function pressPrimary() if autoPress then return end autoPress = true pcall(function() Controller.Dispatch("Primary", true) end) end local function autoFireTick() if closed or not CONFIG.enabled or not CONFIG.autoFire then releasePrimary() drawTool = nil return end local char = LP.Character local tool = char and char:FindFirstChildWhichIsA("Tool") if not tool then releasePrimary() drawTool = nil return end local tuning = shotParams(tool) if not tuning then releasePrimary() drawTool = nil return end local now = os.clock() local origin = aimOrigin(tool) -- Weapons that ramp their power from a draw fire on RELEASE, with the shot's -- speed taken from how long the input was held. Tapping them gives a fraction -- of the charge: the string never comes back, the arrow leaves slow, and a -- weapon with a minimum charge refuses the throw outright. So start the draw, -- hold it for the weapon's own draw time, then release. local drawTime = tonumber(tuning.drawTime) if drawTime and drawTime > 0 then if not autoPress then -- the handler refuses to start a draw inside its own cooldown, so wait -- it out rather than pressing into a dead input if now - lastAutoShot < (tuning.cooldown or 0.25) + 0.02 then return end local solution = planShot(origin, tuning.speedMax or tuning.speed or 250, tuning.gravity or 0) if not shotVerified(solution) then return end pressPrimary() drawTool = tool drawUntil = now + drawTime + 0.06 return end if drawTool ~= tool then releasePrimary() drawTool = nil return end if now < drawUntil then return end -- the draw is complete. If the target just stepped out, keep winding a -- moment longer rather than throwing the shot away, but never hold on. local held = planShot(origin, tuning.speedMax or tuning.speed or 250, tuning.gravity or 0) if not shotVerified(held) and now < drawUntil + 0.4 then return end releasePrimary() drawTool = nil lastAutoShot = now return end -- Magazine weapons fire on press. if now - lastAutoShot < 0.08 then return end -- let the weapon finish its own reload first: pressing into the crank leaves -- it half drawn and burns a shot the server refuses if tool:GetAttribute("Reloading") == true then return end local ammo, magazine = tool:GetAttribute("Ammo"), tool:GetAttribute("MaxAmmo") if type(ammo) == "number" and type(magazine) == "number" and magazine > 0 and ammo <= 0 then return end local solution = planShot(origin, speed, tuning.gravity or 0) if not shotVerified(solution) then return end lastAutoShot = now releasePrimary() pressPrimary() task.delay(0.05, function() if autoPress and not closed then releasePrimary() end end) end task.spawn(function() while not closed do local ok, err = pcall(autoFireTick) if not ok then STATE.lastError = "autofire: " .. tostring(err) end local auraOk, auraErr = pcall(killAuraTick) if not auraOk then STATE.lastError = "killaura: " .. tostring(auraErr) end task.wait(0.05) end end) -- ------------------------------------------------------------------ auto heal -- The server ignores "Use" for a tool that is not the active one: sending the -- payload for a bandage sitting in the backpack (with no eq, with the current -- EquipAck, and with EquipAck + 1) is silently dropped, and a hand-rolled Equip -- loses the race against the client's own equip sequence. So the heal has to make -- the item active first, which means a swap: equip the item, let the game take it, -- then put the weapon back. -- -- Both halves go through the game's own entry points so its state stays coherent. -- Controller.Equip assigns the equip sequence the server matches the payload -- against, and the item's own handler is what sends the Use remote, so no payload -- is hand-built and nothing in the client's bookkeeping is bypassed. local healState = { phase = "idle", untilAt = 0, item = nil, main = nil, last = 0, hp = 0, charges = nil } local function autoHealTick() if closed or not CONFIG.enabled or not CONFIG.autoHeal then healState.phase = "idle" return end local char = LP.Character local hum = char and char:FindFirstChildOfClass("Humanoid") if not hum or hum.Health <= 0 then healState.phase = "idle" return end local now = os.clock() local equippedTool = char:FindFirstChildWhichIsA("Tool") if healState.phase ~= "idle" then if not healState.item or not healState.item.Parent then healState.phase = "idle" return end if healState.phase == "equip" then if now < healState.untilAt then return end -- the game refused the equip (dead, mouse unlocked, item moved) if equippedTool ~= healState.item then healState.phase = "idle" healState.last = now logEvent("heal", "equip refused for " .. tostring(healState.item.Name)) return end -- exactly what the item's own input handler does on a press pcall(function() Controller.Dispatch("Primary", true) end) task.delay(0.05, function() if not closed then pcall(function() Controller.Dispatch("Primary", false) end) end end) healState.phase = "use" healState.untilAt = now + (healState.useTime or 0.7) + 0.35 return end if healState.phase == "use" then -- polled rather than waited out: the item is consumed the moment the -- server takes it, and the weapon goes straight back at that point local charges = healState.item:GetAttribute("Charges") local consumed = charges ~= healState.charges local healed = hum.Health > healState.hp if consumed or healed or now >= healState.untilAt then CAL.heals = (CAL.heals or 0) + 1 if consumed or healed then CAL.healsLanded = (CAL.healsLanded or 0) + 1 logEvent("heal", string.format("%s: hp %.0f -> %.0f, charges %s -> %s", tostring(healState.item.Name), healState.hp, hum.Health, tostring(healState.charges), tostring(charges))) else logEvent("heal", tostring(healState.item.Name) .. " was not consumed") end -- only take the weapon back out if we are still the ones holding -- the item; if the player swapped by hand, leave their choice alone if healState.main and healState.main.Parent and equippedTool == healState.item then pcall(function() Controller.Equip(healState.main) end) end healState.phase = "swapped" healState.untilAt = now + 0.3 end return end if now < healState.untilAt then return end healState.phase = "idle" healState.last = now return end if now - healState.last < (CONFIG.autoHealCooldown or 3) then return end if hum.Health > hum.MaxHealth * ((CONFIG.autoHealBelow or 70) / 100) then return end -- pick a healing item with something left in it, from the backpack or hands local item, useTime local sources = { LP.Backpack, char } for _, container in ipairs(sources) do for _, tool in ipairs(container:GetChildren()) do if not item and tool:IsA("Tool") then local weaponId = tool:GetAttribute("WeaponId") local definition = weaponId and Weapons.Get(weaponId) if definition and definition.kind == "Herbs" then local charges = tool:GetAttribute("Charges") if type(charges) ~= "number" or charges >= 1 then item = tool useTime = definition.tuning and definition.tuning.useTime end end end end end if not item then return end healState.item = item healState.useTime = useTime healState.main = (equippedTool and equippedTool ~= item) and equippedTool or nil healState.hp = hum.Health healState.charges = item:GetAttribute("Charges") healState.phase = "equip" healState.untilAt = now + 0.5 pcall(function() Controller.Equip(item) end) end -- ------------------------------------------------------------------ hvH countermeasures -- Anyone aiming at us only ever sees a delayed, interpolated copy of our -- position, and has to extrapolate that copy forward to place a shot. A smooth -- path makes that extrapolation exact, and per-frame noise is averaged away by -- their fit. What a fit cannot survive is a step: the body is held inside a -- wall-checked offset for a fraction of a second and then snapped to a fresh one -- several studs away. The apparent velocity across a single jump is far larger -- than anything a sampled window can track, so their lead is computed from a -- poisoned estimate and the projectile is thrown at a point we are not at, while -- the authoritative copy the server resolves damage against is the offset one. -- -- The offset is stripped before it is reapplied, so the copies never accumulate -- and the character still walks normally. Our own shots read the muzzle after -- the offset is applied, so they stay exact. local hvRandom = Random.new(os.clock() * 1e6 // 1) local lastOffset = Vector3.new(0, 0, 0) local desyncOffset = Vector3.new(0, 0, 0) local desyncNext = 0 -- Pick a destination that is open floor on every side of the step. Blinking into -- geometry or through a wall is visible to every client watching, and it also -- drops us inside a part where the humanoid cannot move. -- Nearest enemy, used to aim the step and to size it. Only consulted when a new -- offset is chosen, not per frame. local function nearestThreat(root) local best, bestDist, bestPos for _, model in ipairs(targetPool()) do local head, theirRoot = validTarget(model) if head and theirRoot then local delta = theirRoot.Position - root.Position local dist = delta.Magnitude if dist < 90 and (not bestDist or dist < bestDist) then best, bestDist, bestPos = model, dist, theirRoot.Position end end end return best, bestDist, bestPos end -- Jump out of the way when someone nearby attacks. This is deliberately separate from -- the desync: it runs with the desync switched off, it fires on the attack itself -- rather than on arrival, and it holds the displaced position for the whole flight so -- the target stays out of the way for every step of the projectile's path, not just -- at the instant it lands. STATE.armDodge = function(velocity, attackerPos) if not CONFIG.dodge then return end local char = LP.Character local root = char and char:FindFirstChild("HumanoidRootPart") if not root then return end local now = os.clock() -- a rapid weapon is firing again inside this dodge, so a step per shot is the only -- answer: the guard is skipped for those and each step takes a fresh side local forced = STATE.dodgeForce STATE.dodgeForce = nil -- do not restart a dodge that is already running: holding one position is what -- makes it work if not forced and STATE.dodgeUntil and now < STATE.dodgeUntil - 0.05 then -- worth saying out loud: a move that never happened looks exactly like a move that -- failed, and this is the one that decides whether a shot lands if not STATE.skipLogged or now - STATE.skipLogged > 1 then STATE.skipLogged = now logEvent("dodge", string.format("move skipped, body already displaced for another %.0fms", ((STATE.dodgeUntil or now) - now) * 1000)) end return end local low = math.min(CONFIG.dodgeDistanceMin or 6, CONFIG.dodgeDistanceMax or 10) local high = math.max(CONFIG.dodgeDistanceMin or 6, CONFIG.dodgeDistanceMax or 10) local base = root.Position local params = Util.WorldParams(Util.Teammates()) -- The axis is the SHOT'S line, not the shooter's. Whenever a projectile's velocity is -- in hand, that is the direction the arrow is travelling, and a step perpendicular to it -- cannot change how far along the flight the arrow is - it changes only how far off the -- path the body is, which is the whole of what a dodge is. Deriving the axis from the -- shooter's position instead put the step at an angle to the arrow whenever the two -- differed, which at close range and with any lead they do. local pathOrigin = typeof(attackerPos) == "Vector3" and attackerPos or nil local pathDir if typeof(velocity) == "Vector3" then local flat = Vector3.new(velocity.X, 0, velocity.Z) if flat.Magnitude > 0.5 then pathDir = flat.Unit end end if not pathDir and pathOrigin then local flat = Vector3.new(base.X - pathOrigin.X, 0, base.Z - pathOrigin.Z) if flat.Magnitude > 0.5 then pathDir = flat.Unit end end local across if pathDir then across = Vector3.new(-pathDir.Z, 0, pathDir.X) else local a = hvRandom:NextNumber(0, math.pi * 2) across = Vector3.new(math.cos(a), 0, math.sin(a)) end -- And the side is chosen to move AWAY from the line, never across it. This is the part -- that was landing the body on the shot: with the body already off to one side, a step -- to the same side arrives back on the line, and a step to the opposite side crosses it. -- The signed distance from the line says which way is out, and only when the body is -- effectively on the line does the choice fall back to alternating. local side if pathOrigin and pathDir then local rel = Vector3.new(base.X - pathOrigin.X, 0, base.Z - pathOrigin.Z) local signed = rel:Dot(across) if math.abs(signed) > 1 then side = signed > 0 and 1 or -1 end end if not side then side = STATE.dodgeSide and -STATE.dodgeSide or (hvRandom:NextNumber() < 0.5 and 1 or -1) end STATE.dodgeSide = side local dir = across * side -- The length is drawn from the configured range, so the displacement is a different -- size every time; the ends and the midpoint stay as fallbacks for when the drawn one -- lands in geometry. local lengths = { hvRandom:NextNumber(low, high), high, low, (low + high) * 0.5 } -- Where the body has just been. Alternating sides alone is not enough to keep it out of -- its own footprints: left, right, left arrives back at the same left, and with the -- length drawn from a range it can arrive back almost exactly on top of it. So the last -- few landings are remembered and a candidate within a few studs of any of them is -- rejected while anything else is still open - a body that returns to a place it has -- already stood is a body that can be pre-aimed at it. local recent = STATE.recentSteps if recent then local now2 = os.clock() for index = #recent, 1, -1 do if now2 - recent[index].at > 3 then table.remove(recent, index) end end else recent = {} STATE.recentSteps = recent end local function isFresh(step) for _, previous in ipairs(recent) do if (step - previous.step).Magnitude < 3 then return false end end return true end local function place(step) STATE.dodgeOffset = step STATE.dodgeSince = now -- smart dodge: the hold is what the attacker's own weapon and the arc of their shot -- call for, rather than a fixed number local hold = CONFIG.dodgeSmart and STATE.dodgeSmartHold or nil STATE.dodgeSmartHold = nil STATE.dodgeUntil = now + (hold or CONFIG.dodgeHold or 0.4) STATE.dodgeAcross = across recent[#recent + 1] = { step = step, at = now } while #recent > 3 do table.remove(recent, 1) end -- Never sit on one offset for long: a draw can be a full second, and a second parked -- in one place is a second to be re-aimed at, so a fresh side is drawn part way -- through whatever the hold turns out to be. if CONFIG.dodgeSmart and (STATE.dodgeUntil - now) > 0.45 then -- stage three: keep moving inside the hold, spaced off the flight so there are -- roughly two flicks per flight rather than one long park local interval = math.clamp((STATE.dodgeFlight or 0.35) * 0.5, 0.18, 0.6) STATE.dodgeStepAt = now + math.min(interval, (STATE.dodgeUntil - now) * 0.9) end STATE.dodges = (STATE.dodges or 0) + 1 -- who to face for the duration: turned toward the attacker, the step reads as a -- sidestep rather than a slide, and other clients rebuild the body's pose from the -- root they receive, so that is the copy the attacker sees pointed at them STATE.dodgeFace = CONFIG.dodgeFacing and typeof(attackerPos) == "Vector3" and attackerPos or nil -- second step, scheduled inside the flight: their lead was computed at release -- against the position they believed we held, so a fresh displacement while the -- projectile is still travelling is one their aim cannot correct for if CONFIG.dodgeDouble then local arrival if typeof(velocity) == "Vector3" and velocity.Magnitude > 1 and typeof(attackerPos) == "Vector3" then arrival = (base - attackerPos).Magnitude / velocity.Magnitude end if arrival then STATE.dodgeUntil = now + math.max(CONFIG.dodgeHold or 0.4, arrival * 0.55 + 0.12) end STATE.dodgeStepAt = arrival and (now + arrival * 0.55) or nil else STATE.dodgeStepAt = nil end logEvent("dodge", string.format("%.1f studs across, holding %.0fms%s", step.Magnitude, (STATE.dodgeUntil - now) * 1000, STATE.dodgeStepAt and string.format(", re-step in %.0fms", (STATE.dodgeStepAt - now) * 1000) or "")) return true end local function open(step) if workspace:Raycast(base, step, params) then return false end local landing = base + step local floor = workspace:Raycast(landing + Vector3.new(0, 3, 0), Vector3.new(0, -14, 0), params) local headroom = workspace:Raycast(landing + Vector3.new(0, 1, 0), Vector3.new(0, 3.5, 0), params) return floor ~= nil and headroom == nil end local fallback for _, heading in ipairs({ dir, -dir }) do for _, distance in ipairs(lengths) do local step = heading * distance if open(step) then if isFresh(step) then return place(step) end fallback = fallback or step end end end -- every open spot was one just left: take it rather than not move at all, but only after -- exhausting the alternatives if fallback then return place(fallback) end if not STATE.dodgeBlocked or now - STATE.dodgeBlocked > 1 then STATE.dodgeBlocked = now logEvent("dodge", string.format("nowhere lateral to step %.1f studs from here", high)) end return false end -- Real projectile data, read from the game's own weapon table rather than guessed. -- -- The numbers matter more than they look: launch speeds run from 110 studs per second to -- 900, so the same distance is up to an eight-fold difference in how long the shot is in -- the air, and the cadences run from a 150ms short bow to a 1.1 second longbow draw. A -- fixed dodge cannot be right against both ends of that - it either steps too early, which -- is a step they can re-aim at, or too late, which is a step that happens after the arrow -- has already passed. Indexed by projectile kind, which is what an incoming shot carries. STATE.weaponByProjectile = {} ;(function() local ok, list = pcall(function() return Weapons.All() end) if ok and type(list) == "table" then for _, def in pairs(list) do local tuning = type(def) == "table" and def.tuning or nil local kind = tuning and tuning.projectile local speed = tuning and tonumber(tuning.speed or tuning.speedMax) if kind and speed and speed > 0 then local cooldown = tonumber(tuning.cooldown) or 0 local before = STATE.weaponByProjectile[kind] -- Several weapons share a projectile kind - short bow and longbow are both -- "Arrow" - and a kind lookup cannot tell them apart. Keep the most rapid -- one, because the cadence is what decides whether the dodge has to step -- again mid-flight, and stepping again costs nothing while failing to step -- against a 150ms bow costs the exchange. if not before or (cooldown > 0 and (before.cooldown <= 0 or cooldown < before.cooldown)) then STATE.weaponByProjectile[kind] = { speed = speed, cooldown = cooldown, draw = tonumber(tuning.drawTime or tuning.windupTime) or 0, id = def.id or def.kind, } end end end end end)(); -- Ping, as a round trip, refreshed once a second. -- -- This is the number that decides whether a dodge can work at all. A step we apply now is -- only known to the server a round trip later, and the server resolves the hit when its own -- copy of the arrow reaches its own copy of us. So the time actually available to move is -- the travel time minus the round trip, and for anything fast at close range - a musket, a -- flintlock, a knife - that difference goes to zero: the hit is already decided before the -- broadcast that tells us a shot exists has finished arriving. STATE.netRtt = function() local ok, ping = pcall(function() return LP:GetNetworkPing() end) if ok and type(ping) == "number" and ping > 0 then return math.clamp(ping, 0, 0.4) end return 0.05 end -- Where an incoming shot will be, and whether it is coming for us. -- -- The broadcast carries the launch velocity and the gravity the server applies, so the -- trajectory is known rather than estimated: the same arc, evaluated forward against the -- position the body is actually holding. That answers the two questions a dodge needs - will -- it land at all, and if so how long is there - from the shot's own path instead of from a -- speed figure. Distance over speed ignores the arc entirely, and the arc is most of the -- answer at range: a sling stone hangs far past its speed, an arrow with elevation is still -- climbing when a straight-line estimate says it has arrived. -- -- `advance` is how far into the flight the shot already is when we hear about it, which is -- the one-way delay - the same number the ping costs us, spent before we can react. STATE.shotImpact = function(origin, velocity, gravity, advance, target, radius) local down = Vector3.new(0, -(gravity or 0), 0) local speed = velocity.Magnitude local horizon = math.clamp(advance + 1.6, 0.1, 3) -- step by distance along the path, not by clock: at these speeds a time step crosses the -- body entirely between samples local stride = math.max(0.15, math.min(0.45, (radius or 1.7) * 0.3)) local steps = math.clamp(math.ceil(speed * (horizon - advance) / stride), 12, 700) local closest, closestAt = math.huge, nil for i = 0, steps do local t = advance + (horizon - advance) * (i / steps) local point = origin + velocity * t + down * (0.5 * t * t) local distance = (point - target).Magnitude if distance < closest then closest, closestAt = distance, t end if distance <= (radius or 1.7) then return true, t, distance end end return false, closestAt, closest end -- what the game says the attacker is holding: from the weapon id when the windup carried -- one, otherwise from the projectile kind of the shot already in the air STATE.attackerWeapon = {} STATE.weaponFor = function(shooterChar, projectileKind, weaponId) if weaponId then local ok, def = pcall(function() return Weapons.Get(weaponId) end) local tuning = ok and type(def) == "table" and def.tuning or nil if tuning then local speed = tonumber(tuning.speed or tuning.speedMax) if speed and speed > 0 then local info = { speed = speed, cooldown = tonumber(tuning.cooldown) or 0, draw = tonumber(tuning.drawTime or tuning.windupTime) or 0, id = def.id or def.kind } if shooterChar then STATE.attackerWeapon[shooterChar] = info end return info end end end if shooterChar and STATE.attackerWeapon[shooterChar] then return STATE.attackerWeapon[shooterChar] end if projectileKind and STATE.weaponByProjectile[projectileKind] then return STATE.weaponByProjectile[projectileKind] end return nil end local function pickDesync(root) local amount = CONFIG.desyncAmount or 0 if amount <= 0.05 then return Vector3.new(0, 0, 0) end local params = Util.WorldParams(Util.Teammates()) local origin = root.Position -- Size the step from the threat: further away means their lead is longer and a -- larger step is affordable, and someone inside melee reach needs the widest -- step available, since melee is validated against a cone around their aim. local threat, threatDist, threatPos = nearestThreat(root) if threat then local scale = math.clamp((threatDist or 30) / 30, 0.7, 1.5) amount = amount * scale end if STATE.dodgeBoost then amount = amount * 1.35 end -- Aim across the line they are aiming along: a step perpendicular to it turns -- into the largest possible angular error, which is what beats both a lead and -- a melee cone. local preferred if CONFIG.desyncAimAware and threatPos then local line = Vector3.new(threatPos.X - origin.X, 0, threatPos.Z - origin.Z) if line.Magnitude > 0.5 then local across = Vector3.new(-line.Z, 0, line.X).Unit preferred = hvRandom:NextNumber() < 0.5 and across or -across -- a slight along-the-line component so the step is not perfectly regular preferred = (preferred + line.Unit * hvRandom:NextNumber(-0.18, 0.18)).Unit end end local directions = { 0, 45, 90, 135, 180, 225, 270, 315 } if preferred then -- Rank the compass by how perpendicular each bearing is to the line they are -- aiming along, then shuffle among the best few rather than taking the top one. -- Taking the top one made every step identical whenever the threat and the -- geometry held still, which pinned the body to one side instead of moving it -- around. Ties on the compass are a coin toss, so this is where the variation -- has to come from. local across = Vector3.new(-preferred.Z, 0, preferred.X) local acrossAngle = math.atan2(across.Z, across.X) table.sort(directions, function(a, b) local da = math.abs(math.cos(math.rad(a) - acrossAngle)) local db = math.abs(math.cos(math.rad(b) - acrossAngle)) return da < db end) -- Six of the eight bearings, not four: four kept every step inside the narrow -- perpendicular band, which reads as sliding sideways rather than moving around. local shortlist = { directions[1], directions[2], directions[3], directions[4], directions[5], directions[6] } for i = #shortlist, 2, -1 do local j = hvRandom:NextInteger(1, i) shortlist[i], shortlist[j] = shortlist[j], shortlist[i] end directions = shortlist end local start = 1 for index = 1, #directions do local degrees = directions[(start + index - 2) % #directions + 1] local radians = math.rad(degrees) local step = Vector3.new(math.cos(radians), 0, math.sin(radians)) * amount -- the line has to be clear, and the landing spot has to be supported if not workspace:Raycast(origin, step, params) then local landing = origin + step local floor = workspace:Raycast(landing + Vector3.new(0, 3, 0), Vector3.new(0, -12, 0), params) local headroom = workspace:Raycast(landing + Vector3.new(0, 1, 0), Vector3.new(0, 3.5, 0), params) if floor and not headroom then -- Height as well as distance. The body they are solving against is narrow -- vertically and they lead along the ground, so a few studs of height buys -- more miss than the same few studs sideways, and it stacks with them. if CONFIG.desyncVertical and (CONFIG.desyncVerticalAmount or 0) > 0.2 then local lift = (CONFIG.desyncVerticalAmount or 2.5) * hvRandom:NextNumber(0.75, 1.25) local raised = step + Vector3.new(0, lift, 0) if not workspace:Raycast(landing, Vector3.new(0, lift + 1, 0), params) then return raised end end return step end end end return Vector3.new(0, 0, 0) end local function hvhTick() if closed then return end local char = LP.Character local root = char and char:FindFirstChild("HumanoidRootPart") if not root then return end if STATE.pendingDodge and os.clock() >= STATE.pendingDodge.at then local pending = STATE.pendingDodge STATE.pendingDodge = nil -- restore what this move was planned with, so nothing that happened in between can -- have taken it away if pending.force then STATE.dodgeForce = true end if pending.hold then STATE.dodgeSmartHold = pending.hold end if STATE.armDodge then STATE.armDodge(nil, pending.position) end end local dodging = STATE.dodgeUntil and os.clock() < STATE.dodgeUntil local hopping = STATE.meleeOffset and STATE.meleeUntil and os.clock() < STATE.meleeUntil local jittering = CONFIG.jitterDodge and STATE.jitterUntil and os.clock() < STATE.jitterUntil local wanted = CONFIG.desync or dodging or hopping or jittering if not wanted and lastOffset == Vector3.new(0, 0, 0) then return end -- Strip the previous offset first: applying a fresh offset to the already -- offset position walks the character across the map and breaks movement. local clean = root.Position - lastOffset local offset = Vector3.new(0, 0, 0) local now = os.clock() if CONFIG.desync and not dodging then -- Walk-only: while standing still the character is left exactly where the game -- puts it, so a stationary player has no displacement at all. The moment they -- move, the next frame picks a step up straight away. local moving = true if CONFIG.desyncWalkOnly then local humanoid = char:FindFirstChildOfClass("Humanoid") local velocity = root.AssemblyLinearVelocity local flatSpeed = Vector3.new(velocity.X, 0, velocity.Z).Magnitude local input = humanoid and humanoid.MoveDirection.Magnitude or 0 moving = input > 0.1 or flatSpeed > 1 if not moving then desyncOffset = Vector3.new(0, 0, 0) desyncNext = 0 end end local dodge = false if CONFIG.desyncReactive and STATE.dodgeAt and now >= STATE.dodgeAt then dodge = true STATE.dodgeAt = nil STATE.dodgeBoost = true end if dodge or now >= desyncNext then -- Vary the interval. A held offset already poisons their lead, but a fixed -- cadence is the one part of it that stays fittable - measured across steps, -- a regular interval is a signature. Randomising it means the next jump has no -- time they can be ready for. local jitter = math.clamp(CONFIG.desyncJitter or 0, 0, 0.6) local interval = (CONFIG.desyncInterval or 0.18) * (1 + hvRandom:NextNumber(-jitter, jitter)) desyncNext = now + math.max(0.05, interval) desyncOffset = pickDesync(root) STATE.dodgeBoost = nil end if moving then offset = desyncOffset end elseif not dodging then desyncOffset = Vector3.new(0, 0, 0) end -- The dodge rides on top of the desync, and works with the desync switched off. -- It eases in and out instead of snapping: a hard jump would move the camera a -- frame before the correction catches up, and a single 14-stud teleport is exactly -- the shape of movement a server is likely to object to. if dodging and STATE.dodgeOffset then -- Second step, inside the flight. Their lead was fixed at release, so moving again -- now is movement their committed shot cannot follow - the first step only has to -- beat where they thought we were, and this one beats where the arrow is going. if (CONFIG.dodgeDouble or CONFIG.dodgeSmart) and STATE.dodgeStepAt and now >= STATE.dodgeStepAt then STATE.dodgeStepAt = nil local params = Util.WorldParams(Util.Teammates()) local flip if CONFIG.dodgeSmart and STATE.dodgeAcross then -- the opposite side to the one being held, always: never the same side twice, -- so the body crosses the line rather than drifting along one end of it local low, high = CONFIG.dodgeDistanceMin or 6, CONFIG.dodgeDistanceMax or 10 if low > high then low, high = high, low end local side = STATE.dodgeSide and -STATE.dodgeSide or (hvRandom:NextNumber() < 0.5 and 1 or -1) STATE.dodgeSide = side -- same rule as a fresh dodge: nothing within a few studs of the last few -- landings, drawn again until something is open for _ = 1, 4 do local attempt = STATE.dodgeAcross * side * hvRandom:NextNumber(low, high) local fresh = true for _, previous in ipairs(STATE.recentSteps or {}) do if (attempt - previous.step).Magnitude < 3 then fresh = false break end end flip = attempt if fresh then break end end else -- back across the first step: the body ends twice the displacement from -- where it started, on the far side of the point their lead was aimed at flip = -STATE.dodgeOffset end if not workspace:Raycast(clean, flip, params) then local landing = clean + flip local floor = workspace:Raycast(landing + Vector3.new(0, 3, 0), Vector3.new(0, -14, 0), params) local headroom = workspace:Raycast(landing + Vector3.new(0, 1, 0), Vector3.new(0, 3.5, 0), params) if floor and not headroom then STATE.dodgeOffset = flip STATE.dodgeSince = now STATE.dodgeSteps = (STATE.dodgeSteps or 1) + 1 STATE.recentSteps = STATE.recentSteps or {} STATE.recentSteps[#STATE.recentSteps + 1] = { step = flip, at = now } while #STATE.recentSteps > 3 do table.remove(STATE.recentSteps, 1) end -- still a long way to go: draw again rather than park here, spaced off the -- flight time so the body keeps moving for as long as the shot is flying if CONFIG.dodgeSmart and (STATE.dodgeUntil - now) > 0.45 then local interval = math.clamp((STATE.dodgeFlight or 0.35) * 0.5, 0.18, 0.6) STATE.dodgeStepAt = now + math.min(interval, (STATE.dodgeUntil - now) * 0.9) end logEvent("dodge", string.format("re-stepped %.1f studs, %.0fms left of the hold", flip.Magnitude, (STATE.dodgeUntil - now) * 1000)) end end end local ramp = STATE.dodgeRamp or CONFIG.dodgeRamp or 0 local factor = 1 if ramp > 0.001 then local rampIn = (now - (STATE.dodgeSince or now)) / ramp local rampOut = ((STATE.dodgeUntil or now) - now) / ramp factor = math.clamp(math.min(rampIn, rampOut), 0, 1) end offset = offset + STATE.dodgeOffset * factor STATE.dodgeBodyFactor = factor STATE.dodgeBodyOn = true else STATE.dodgeOffset = nil STATE.dodgeSince = nil end if STATE.meleeOffset and STATE.meleeUntil and now < STATE.meleeUntil then offset = offset + STATE.meleeOffset end -- Jitter dodge: a fresh side and a fresh length every interval, so no position survives -- long enough to be aimed at. It replaces what the desync and the dodge were doing rather -- than stacking with them - two offsets at once is still one position. if jittering and STATE.jitterAcross and now >= (STATE.jitterNext or 0) then STATE.jitterNext = now + math.max(0.003, CONFIG.jitterInterval or 0.01) local low = math.min(CONFIG.jitterMin or 3, CONFIG.jitterMax or 15) local high = math.min(math.max(CONFIG.jitterMin or 3, CONFIG.jitterMax or 15), STATE.jitterReach or 15) if high < low then high = low end -- alternate: consecutive positions are on opposite sides and cannot coincide local side = STATE.jitterSide and -STATE.jitterSide or (hvRandom:NextNumber() < 0.5 and 1 or -1) STATE.jitterSide = side STATE.jitterOffset = STATE.jitterAcross * side * hvRandom:NextNumber(low, high) STATE.jitterTicks = (STATE.jitterTicks or 0) + 1 end if jittering and STATE.jitterOffset then offset = STATE.jitterOffset end -- the dodge rides on the desync, but the two must not stack into a jump nobody -- would mistake for moving: cap the total at a bit over the larger of the two local cap = math.max((CONFIG.desyncAmount or 0) + (CONFIG.desyncVertical and (CONFIG.desyncVerticalAmount or 0) or 0), CONFIG.dodgeDistanceMax or 10, CONFIG.meleeHop and (CONFIG.meleeHopHeight or 0) or 0, CONFIG.jitterMax or 15) * 1.2 if offset.Magnitude > cap then offset = offset.Unit * cap end STATE.appliedOffset = offset if offset == Vector3.new(0, 0, 0) and lastOffset == Vector3.new(0, 0, 0) then return end local frame = root.CFrame local target = clean + offset -- While dodging, face whoever we are dodging: turned toward them, level, position -- displaced. Other clients rebuild the body's pose from the root they receive, so -- that is the copy the attacker and the server see. local face = STATE.dodgeFace local faced = false if dodging and face and CONFIG.dodgeFacing then local flat = Vector3.new(face.X - target.X, 0, face.Z - target.Z) if flat.Magnitude > 0.5 then root.CFrame = CFrame.lookAt(target, target + flat.Unit) faced = true end end if not faced then -- otherwise keep the body's own rotation: only the position is displaced root.CFrame = CFrame.new(target) * (frame - frame.Position) end lastOffset = offset end -- The camera is placed off the character, so it inherits the body's displacement -- and the whole view jumps with it. Pull the camera back by the same vector and our -- screen sits exactly where it would with the desync off, while the displaced body -- is still the one replicated to everyone else and resolved against by the server. -- -- The camera script binds at Camera + 1 and re-derives the camera from the body -- every frame, so this has to be the next thing after it. It reads the offset the -- body is currently carrying, which is the same value that camera was built from. local CAMERA_STEP = "TribesOverlayDesyncCamera" RunService:BindToRenderStep(CAMERA_STEP, Enum.RenderPriority.Camera.Value + 2, function() if closed then return end if STATE.cameraStepOff then return end local blend = math.clamp((CONFIG.desyncLocalSteady or 0) / 100, 0, 1) -- The desync follows the local view compensation setting. A dodge does not: "steady -- camera on dodge" means the view is held completely still through the step, and with it -- off the view travels with the body so the step reads as a sidestep. Leaving a dodge on -- the desync's setting is what let 85% of an 18-stud flick land in the view. if STATE.dodgeUntil and os.clock() < STATE.dodgeUntil then blend = CONFIG.dodgeSteadyCamera and 1 or 0 end -- Through a jitter the camera is corrected at FULL strength, whatever the local view -- compensation is set to. A shake is tens of studs, every few milliseconds: leaving any of -- that uncorrected is a view that thrashes, and the desync's compensation setting is about -- whether the desync is visible, not about whether a shake should be. This step runs after -- the game's own camera script re-derives the view from the body, so it is the correction -- that actually lands. if STATE.jitterUntil and os.clock() < STATE.jitterUntil then blend = 1 end if blend <= 0 or lastOffset == Vector3.new(0, 0, 0) then return end local camera = workspace.CurrentCamera if not camera then return end local correction = lastOffset * blend local ok = pcall(function() camera.CFrame = camera.CFrame - correction end) if not ok then STATE.cameraStepFails = (STATE.cameraStepFails or 0) + 1 if STATE.cameraStepFails >= 5 then STATE.cameraStepOff = true logEvent("render", "camera step lacks instance access, disabled") end return end -- The body we are actually shown is not the character at all: the game hides -- the character's parts from its own client and draws a separate R15 clone -- under the camera, positioned from the character each frame. That clone moves -- with the desync, so it is the one thing that would still visibly jump. It is -- rebuilt from the character every frame, so a per-frame correction neither -- accumulates nor fights anything. local drawn = camera:FindFirstChild("TribesDrawnBody") if drawn then for _, item in ipairs(drawn:GetDescendants()) do if item:IsA("BasePart") then item.CFrame = item.CFrame - correction end end end end) -- ------------------------------------------------------------------ render loop -- One frame of everything the overlay draws. Kept as a function so it can be called -- either straight from the render step or, if the environment refuses instance access -- from render callbacks, from a coroutine created here at load time (which carries the -- loading thread's capability instead). local function renderFrame(dt) -- Some executors run callbacks (render steps, coroutines) at a lower capability -- than the console thread, which makes instance writes inside them fail with -- "lacking capability Plugin" even though the same write works from the console. -- Raise it back every frame; harmless where the functions do not exist. pcall(setthreadidentity, 8) pcall(setthreadcontext, 8) -- one-off check: can this thread still create and parent after the load finished? if not STATE.renderSelfTest then STATE.renderSelfTest = true local steps = {} local function attempt(name, fn) local ok, err = pcall(fn) steps[#steps + 1] = string.format("%s:%s", name, ok and "ok" or ("FAIL " .. tostring(err):gsub("%s+", " "):sub(1, 40))) end attempt("frame", function() local f = Instance.new("Frame") f.BorderSizePixel = 0 f.BackgroundColor3 = Color3.new(1, 1, 1) f.Visible = false f.Parent = espGui f:Destroy() end) attempt("anchor", function() local f = Instance.new("Frame") f.AnchorPoint = Vector2.new(0.5, 0.5) f.Parent = espGui f:Destroy() end) attempt("uicorner", function() local f = Instance.new("Frame") local c = Instance.new("UICorner") c.CornerRadius = UDim.new(0, 4) c.Parent = f f.Parent = espGui f:Destroy() end) attempt("uistroke", function() local f = Instance.new("Frame") local st = Instance.new("UIStroke") st.Color = Color3.new(1, 1, 1) st.Parent = f f.Parent = espGui f:Destroy() end) attempt("textlabel", function() local t = Instance.new("TextLabel") t.BackgroundTransparency = 1 t.Font = Enum.Font.Code t.TextSize = 13 t.TextColor3 = Color3.new(1, 1, 1) t.TextStrokeTransparency = 0.4 t.TextStrokeColor3 = Color3.fromRGB(0, 0, 0) t.TextXAlignment = Enum.TextXAlignment.Center t.TextYAlignment = Enum.TextYAlignment.Center t.AnchorPoint = Vector2.new(0.5, 0.5) t.Size = UDim2.fromOffset(280, 17) t.Visible = false t.Parent = espGui t:Destroy() end) attempt("highlight", function() local h = Instance.new("Highlight") h.Enabled = false h.FillTransparency = 1 h.OutlineTransparency = 0 h.DepthMode = Enum.HighlightDepthMode.AlwaysOnTop h.Parent = espGui h:Destroy() end) attempt("ugradient", function() local f = Instance.new("Frame") local g = Instance.new("UIGradient") g.Color = ColorSequence.new(Color3.new(1,1,1), Color3.new(0,0,0)) g.Parent = f f.Parent = espGui f:Destroy() end) STATE.renderSelfTestResult = table.concat(steps, " ") end local tickOk, tickErr = pcall(hvhTick, dt) if not tickOk then STATE.tickError = tostring(tickErr) else STATE.tickError = nil end local healOk, healErr = pcall(autoHealTick) if not healOk then STATE.lastError = "autoheal: " .. tostring(healErr) end local ok, err = xpcall(ESPRender, function(message) local trace = debug and debug.traceback and debug.traceback("", 2) or "" return tostring(message) .. " @ " .. tostring(trace):gsub("%s+", " "):sub(1, 220) end, dt) if not ok then local text = tostring(err) STATE.espError = text -- "lacking capability" means this thread may not touch instances at all: it is -- not a bug in the drawing, it is the thread. Hand the work to the loader thread. if not STATE.renderProxy and (text:find("capability") or text:find("cannot access")) then STATE.renderProxy = true logEvent("render", "render callback lacks instance access, moving to the loader thread") end local now = os.clock() if not STATE.espErrorAt or now - STATE.espErrorAt > 1 then STATE.espErrorAt = now end else STATE.espError = nil end end local renderConn = RunService.RenderStepped:Connect(function(dt) if STATE.renderProxy then -- just a heartbeat: the drawing happens on the loader thread below STATE.proxyTick = (STATE.proxyTick or 0) + 1 STATE.proxyDt = dt return end renderFrame(dt) end) -- Created during the load, so it runs with the loading thread's capability rather than -- the render callback's. Idle unless the proxy above asked for it. task.spawn(function() local seen = -1 while not closed do if STATE.renderProxy and STATE.proxyTick ~= seen then seen = STATE.proxyTick local ok, err = pcall(renderFrame, STATE.proxyDt or 0.016) if not ok then STATE.proxyError = tostring(err) end end task.wait() end end) -- ------------------------------------------------------------------ api / unload local api local function unload() if closed then return end closed = true CONFIG.enabled = false VIS.enabled = false -- never leave the trigger held or a draw winding releasePrimary() drawTool = nil pcall(function() RunService:UnbindFromRenderStep(CAMERA_STEP) end) -- put the head back and unhide it renderConn:Disconnect() denialConnection:Disconnect() if STATE.unreliableFx then pcall(function() STATE.unreliableFx:Disconnect() end) end hitConnection:Disconnect() if Effects.Projectile == projectileHook then Effects.Projectile = originalProjectile end if originalPellets and Effects.Pellets == pelletsHook then Effects.Pellets = originalPellets end if getrawmetatable(game).__namecall == namecallHook then hookmetamethod(game, "__namecall", originalNamecall) end pending = nil for _, hook in ipairs(handlerHooks) do hook.table[hook.key] = hook.original end for model, s in pairs(ESP.pool) do destroyState(s) ESP.pool[model] = nil end table.clear(ESP.pool) if ESP.realBody then local rb = ESP.realBody for _, key in ipairs({ "real", "shown", "tile", "jitAnchor", "pip", "capA", "capB" }) do pcall(function() rb[key]:Destroy() end) end pcall(function() rb.ring:Destroy() end) pcall(function() rb.tag:Destroy() end) ESP.realBody = nil end for _, part in ipairs(ESP.tracerPool or {}) do pcall(function() part:Destroy() end) end for _, entry in ipairs(ESP.markerPool or {}) do pcall(function() entry.part:Destroy() end) end pcall(function() espGui:Destroy() end) pcall(function() local lib = getgenv().Library if lib and lib.Unload then lib:Unload() end end) pcall(function() sg:Destroy() end) if getgenv().TribesOverlay == api then getgenv().TribesOverlay = nil end if getgenv().SilentAim == api then getgenv().SilentAim = nil end end api = { CONFIG = CONFIG, VIS = VIS, STATE = STATE, UI = UI, ESP = ESP, solve = aimDirection, hasLine = hasLine, -- the ported machinery, exposed so it can be measured rather than trusted solveIntercept = solveIntercept, pathCheck = pathCheck, shotVerified = shotVerified, track = function() return TRACK end, -- the whole shot plan, so the aim can be checked without firing plan = function(origin, speed, gravity) return planShot(origin, speed, gravity) end, wallStats = function() return WALL end, -- run the wallbang decision by hand against a solution table, the same call the shot -- path makes, so the origin it picks can be checked without a shot in the air wallCheck = function(origin, solution, speed, gravity) return applyWallbang(origin, solution, speed, gravity) end, wallOrigin = function(origin, aimPoint, model, maxOffset) return findWallbangOrigin(origin, aimPoint, model, maxOffset or CONFIG.wallbangMax) end, viewLag = viewLag, worldClear = worldClear, selectTarget = function(origin) return selectTarget(origin, nil) end, saveSettings = function() local ok, message = saveConfig() return ok, message end, loadSettings = function() local ok, message = loadConfig() uiRefresh() return ok, message end, -- exposed so the drawing can be run by hand from the console thread, which is how -- a build that draws in the render callback can be told apart from one that draws renderFrame = function(dt) return renderFrame(dt or 0.016) end, -- Runs the drawing one stage at a time, so an environment that refuses a -- specific operation can be pinned to it. Defined here as a closure because the -- main chunk is at its local limit. -- arms a reactive dodge by hand, the same state the incoming-attack handlers set, -- so the consumer side can be checked without somebody shooting at us -- delegates to the real thing: an earlier stub here only set a legacy field, so -- anything calling it (including my own tests) did nothing armDodge = function(velocity, attackerPos) return STATE.armDodge(velocity, attackerPos) end, tickError = function() return STATE.tickError end, -- feed a synthetic Swing through the real handler: exercises the trigger, the -- reach gate, the placement and the jump, which is the whole chain espBisect = function() local out = {} local function stage(name, fn) local ok, err = xpcall(fn, function(message) local trace = debug and debug.traceback and debug.traceback("", 2) or "" return tostring(message) .. " @ " .. tostring(trace):gsub("%s+", " "):sub(1, 160) end) out[#out + 1] = string.format("%-16s %s%s", name, ok and "ok" or "FAIL", ok and "" or (" " .. tostring(err):sub(1, 200))) end local model = targetPool()[1] stage("viewport", function() local _ = Camera.ViewportSize end) stage("camera cframe", function() local _ = Camera.CFrame end) stage("project point", function() local _ = Camera:WorldToViewportPoint(Vector3.new(0, 0, 0)) end) stage("fov ring write", function() fovRing.Visible = true fovRing.Position = UDim2.fromOffset(1, 1) fovRing.Size = UDim2.fromOffset(2, 2) fovRing.Visible = VIS.fovRing end) stage("target pool", function() local _ = #targetPool() end) if model then stage("valid target", function() local _ = validTarget(model) end) stage("bounding box", function() local ok, cf, size = pcall(model.GetBoundingBox, model) if not ok then error(cf) end local _ = projectBox(cf, size) end) stage("build state", function() local _ = stateFor(model) end) local state = ESP.pool[model] if state then stage("hide all", function() hideAll(state) end) stage("line write", function() state.full[1].Visible = true state.full[1].Position = UDim2.fromOffset(1, 1) state.full[1].Visible = false end) stage("name write", function() state.name.Text = "x" state.name.Visible = false end) stage("chams write", function() state.chams.Adornee = model state.chams.FillColor = Color3.new(1, 0, 0) state.chams.Enabled = false end) end end return out end, espRender = function(dt) return ESPRender(dt or 0.016) end, solveSpeed = function(origin, direction, impact, gravity) return solveLaunchSpeed(origin, direction, impact, gravity) end, -- Walk the operations the interface actually performs and report which one the -- environment refuses. For a build where the ESP draws nothing, this names the -- blocked access instead of leaving it to guesswork from a message alone. probe = function() local out = {} local function step(name, fn) local ok, err = pcall(fn) out[#out + 1] = string.format("%-18s %s%s", name, ok and "ok" or "FAIL", ok and "" or (" " .. tostring(err):gsub("%s+", " "):sub(1, 120))) end step("viewport", function() local _ = Camera.ViewportSize end) step("gui frame", function() local f = Instance.new("Frame") f.Parent = espGui f:Destroy() end) step("ui stroke", function() local f = Instance.new("Frame") local u = Instance.new("UIStroke") u.Parent = f f.Parent = espGui f:Destroy() end) step("text label", function() local t = Instance.new("TextLabel") t.Text = "x" t.Parent = espGui t:Destroy() end) step("highlight", function() local h = Instance.new("Highlight") h.Parent = espGui h:Destroy() end) step("billboard gui", function() local b = Instance.new("BillboardGui") b.Parent = espGui b:Destroy() end) step("world part", function() local x = Instance.new("Part") x.Parent = workspace x:Destroy() end) step("collection tag", function() local _ = CollectionService:HasTag(workspace, "Combatant") end) step("read target", function() local m = targetPool()[1] if m then local h = m:FindFirstChild("Head") if h then local _ = h.Position end local hum = m:FindFirstChildOfClass("Humanoid") if hum then local _ = hum.Health end end end) step("write target part", function() local m = targetPool()[1] local part = m and (m:FindFirstChild("UpperTorso") or m:FindFirstChild("Head")) if part then local was = part.Material part.Material = Enum.Material.Plastic part.Material = was end end) step("write highlight", function() local m = targetPool()[1] if m then local h = Instance.new("Highlight") h.Parent = espGui h.Adornee = m h.FillColor = Color3.new(1, 0, 0) h.DepthMode = Enum.HighlightDepthMode.AlwaysOnTop h:Destroy() end end) return out end, -- Test hooks that cannot leak. A value set here is remembered, restored by testEnd -- whatever happens to the caller, and excluded from any save in the meantime, so -- changing what the overlay aims at during a test cannot end up in the saved -- settings. Anything toggling team filtering or the aim cone goes through this. testSet = function(values) STATE.testPins = STATE.testPins or {} for key, value in pairs(values) do if STATE.testPins[key] == nil then STATE.testPins[key] = CONFIG[key] end CONFIG[key] = value end return true end, testEnd = function() local pins = STATE.testPins STATE.testPins = nil if not pins then return false end for key, value in pairs(pins) do CONFIG[key] = value end saveConfig() return true end, -- shots taken at us, and how many of them landed, per shooter dodgeScore = function() local shots, taken = CAL.shotsFor or {}, CAL.takenFor or {} local out = {} for name, count in pairs(shots) do local landed = taken[name] or 0 out[#out + 1] = string.format("%s: %d shots at us, %d landed (%.0f%%)", name, count, landed, count > 0 and 100 * landed / count or 0) end table.sort(out) return out, { shots = STATE.shotsAtUs or 0, landed = CAL.taken or 0, predictedHit = CAL.takenPredictedHit or 0, predictedMiss = CAL.takenPredictedMiss or 0 } end, solvedSpeeds = function() local out = {} for kind, list in pairs(CAL.solvedSpeed or {}) do local sum, n = 0, 0 local copy = table.clone(list) table.sort(copy) for _, v in ipairs(copy) do sum += v n += 1 end out[kind] = { median = copy[math.floor(n / 2) + 1] or 0, mean = n > 0 and sum / n or 0, n = n } end return out end, speedReport = function() local out = {} for kind, list in pairs(CAL.speedSamples or {}) do local sum, n = 0, 0 for _, v in ipairs(list) do sum += v n += 1 end out[kind] = { mean = n > 0 and sum / n or 0, n = n } end return out end, isAirborne = function(model) return model and targetIsAirborne(model) or false end, targetState = function(model) local root = model and model:FindFirstChild("HumanoidRootPart") if not root then return nil end return targetState(model, root) end, checkPath = pathCheck, checkPath = hasLine, planMortar = function(model) local char = LP.Character local tool = char and char:FindFirstChildWhichIsA("Tool") local tuning = shotParams(tool) if not tuning then return nil, "No equipped bow" end local origin = Util.ShotOrigin(tool, "Muzzle") local target if model then local head, root, player, dummy, hitbox = validTarget(model) if head then target = { model = model, head = head, root = root, part = aimPartFor(head, hitbox, player, dummy, root) } end end target = target or selectTarget(origin) if not target then return nil, "No target in FOV" end local speed = tuning.speedMax or tuning.speed or 250 local gravity = tuning.gravity or 60 local point = target.part.Position local vel = target.velocity or Vector3.new(0, 0, 0) local direction = solveShot(origin, point + vel * viewLag(), Vector3.new(vel.X, 0, vel.Z), 1, speed, gravity) direction = direction or aimDirection(origin, point, speed, gravity, "high") if not direction then return nil, "No direction" end return { target = target.model, direction = direction, speed = speed, gravity = gravity, charge = 1, drawSeconds = tuning.drawTime or 0, distance = (point - origin).Magnitude } end, resolve = function(charge) local char = LP.Character local tool = char and char:FindFirstChildWhichIsA("Tool") local tuning = shotParams(tool) if not tuning then return nil end local _, speed = Combat.DrawnShot(tuning, charge or 1) local origin = Util.ShotOrigin(tool, "Muzzle") return planShot(origin, speed, tuning.gravity or 60) end, stats = function() local tracked, drawn, objects = 0, 0, 0 for _, s in pairs(ESP.pool) do tracked += 1 local any = false for _, list in ipairs({ s.full, s.corners, s.skel }) do for _, f in ipairs(list) do objects += 1 if f.Visible then any = true end end end for _, f in ipairs({ s.fill, s.hbBg, s.hbFill, s.dot, s.snap, s.name, s.dist, s.wpn }) do objects += 1 if f.Visible then any = true end end if any then drawn += 1 end end return { tracked = tracked, drawn = drawn, objects = objects, pool = STATE.pool, ring = fovRing.Visible, radius = fovRing.Visible and fovPixelRadius() or 0, redirected = STATE.redirected } end, hideMenu = function() if uiWindow then uiWindow:SetOpen(false) end end, showMenu = function() if uiWindow then uiWindow:SetOpen(true) end end, unload = unload, } STATE.loadedAt = os.clock() getgenv().TribesOverlay = api getgenv().SilentAim = api return { installed = true, gui = sg, place = game.PlaceId }