diff --git a/BladeBall/JustHub b/BladeBall/JustHub new file mode 100644 index 0000000..3abb592 --- /dev/null +++ b/BladeBall/JustHub @@ -0,0 +1,449 @@ +local RunService = game:GetService("RunService") +local Players = game:GetService("Players") +local TweenService = game:GetService("TweenService") +local VirtualInputManager = game:GetService("VirtualInputManager") + +local Player = Players.LocalPlayer +if not Player then error("LocalPlayer tidak ditemukan!") end + +-- Cache fungsi matematika untuk performa +local min = math.min +local max = math.max +local abs = math.abs +local deg = math.deg +local acos = math.acos +local sqrt = math.sqrt +local clamp = math.clamp + +-- Flag debug (set false jika tidak ingin banyak log) +local DEBUG = true +local function AdvancedLog(level, message, data) + if not DEBUG then return end + local timestamp = os.date("%Y-%m-%d %H:%M:%S") + print(string.format("[%s] [%s]: %s", timestamp, level, message)) + if data then + for k, v in pairs(data) do + print(string.format(" %s: %s", k, tostring(v))) + end + end +end + +-- Konstanta State +local STATE_IDLE = "Idle" +local STATE_TRACKING = "Tracking" +local STATE_ANTICIPATING = "Anticipating" +local STATE_PARIED = "Parried" +local STATE_COOLDOWN = "Cooldown" +local STATE_SPAM = "Spam" + +local MAX_IMPACT_TIME = 4.0 -- Maksimum waktu impact (detik) +local INCOMING_ANGLE_THRESHOLD = 20 -- Threshold sudut (derajat) + +--------------------------------- +-- Advanced Parry Controller (State Machine) +--------------------------------- +local AdvancedParryController = {} +AdvancedParryController.__index = AdvancedParryController + +function AdvancedParryController.new() + local self = setmetatable({}, AdvancedParryController) + self.Player = Player + self.State = STATE_IDLE + self.LastParryTime = tick() + self.CooldownTime = 0.75 -- Cooldown normal (detik) + self.BaseThreshold = 0.45 -- Threshold dasar (detik) + self.BallStopped = false + self.PreviousVelocity = nil + self.SmoothedAccel = Vector3.new(0, 0, 0) + self.CurrentBall = nil + self.Connection = nil + self.LastSpamTime = 0 + self.BaseSpamInterval = 0.1 + + -- GUI Full Screen Popup + self.FullScreenGui = nil + self.AdditionalInfoLabel = nil + + -- Overpower Mode: parry akan menjadi jauh lebih agresif dan responsif + self.OverpowerMode = true + self.OverpowerThresholdMultiplier = 0.5 -- Mengurangi threshold agar trigger lebih cepat + self.OverpowerCooldown = 0.1 -- Cooldown sangat rendah pada mode ini + + return self +end + +-- Dapatkan ping lokal (detik) +function AdvancedParryController:GetLocalPing() + local success, ping = pcall(function() return self.Player:GetNetworkPing() end) + if not success then + AdvancedLog("WARN", "Gagal mendapatkan ping, default 0.1 detik.", nil) + return 0.1 + end + return ping or 0.1 +end + +-- Hitung threshold dinamis berdasarkan ping (dikurangi jika OverpowerMode aktif) +function AdvancedParryController:ComputeDynamicThreshold() + local adjustmentFactor = 0.5 + local threshold = self.BaseThreshold + (self:GetLocalPing() * adjustmentFactor) + if self.OverpowerMode then + threshold = threshold * self.OverpowerThresholdMultiplier + end + return threshold +end + +-- Seleksi bola target terbaik (paling mengancam) dari folder "Balls" +function AdvancedParryController:SelectTargetBall() + local ballsFolder = workspace:FindFirstChild("Balls") + local bestBall = nil + local bestTImpact = math.huge + if ballsFolder then + local character = self.Player.Character + local HRP = character and character:FindFirstChild("HumanoidRootPart") + if not HRP then return nil, math.huge end + for _, ball in ipairs(ballsFolder:GetChildren()) do + if ball:GetAttribute("realBall") and ball:GetAttribute("target") == self.Player.Name then + local distance = (HRP.Position - ball.Position).Magnitude + local ballVelocity + local success = pcall(function() ballVelocity = ball.zoomies.VectorVelocity end) + if success and typeof(ballVelocity) == "Vector3" then + -- Untuk seleksi, asumsikan percepatan nol + local tImpact = self:PredictImpactTime(distance, ballVelocity, Vector3.new(0,0,0)) + if tImpact < bestTImpact then + bestTImpact = tImpact + bestBall = ball + end + end + end + end + end + return bestBall, bestTImpact +end + +-- Reset koneksi event bila diperlukan +function AdvancedParryController:ResetConnection() + if self.Connection then + self.Connection:Disconnect() + self.Connection = nil + end +end + +-- Inisialisasi listener bola untuk mendeteksi perubahan atribut target +function AdvancedParryController:InitializeBallListener() + self:ResetConnection() + local ball = self:SelectTargetBall() -- Menggunakan target terbaik + if ball then + self.CurrentBall = ball + self.Connection = ball:GetAttributeChangedSignal("target"):Connect(function() + self.State = STATE_TRACKING + AdvancedLog("INFO", "Atribut target bola berubah, reset state ke TRACKING.", {Ball = ball.Name}) + end) + AdvancedLog("INFO", "Listener bola diinisialisasi.", {Ball = ball.Name}) + else + self.CurrentBall = nil + AdvancedLog("WARN", "Bola tidak ditemukan. Listener tidak diinisialisasi.", nil) + end +end + +-- Inisialisasi GUI full-screen (popup hitam) dengan animasi smooth. +-- Tampilan: layar penuh yang sedikit transparan, di tengah ada tulisan "Status : Auto Parry", +-- label tambahan untuk informasi penting (misal: Ping & State) dan tombol Exit. +function AdvancedParryController:InitializeGUI() + local playerGui = self.Player:WaitForChild("PlayerGui") + local screenGui = Instance.new("ScreenGui") + screenGui.Name = "FullScreenPopupGui" + screenGui.ResetOnSpawn = false + screenGui.IgnoreGuiInset = true -- Menutupi seluruh layar (termasuk area inset) + screenGui.Parent = playerGui + + -- Frame full screen dengan background hitam (mulai dari transparansi penuh untuk animasi) + local fullScreenFrame = Instance.new("Frame") + fullScreenFrame.Name = "FullScreenFrame" + fullScreenFrame.BackgroundColor3 = Color3.new(0, 0, 0) + fullScreenFrame.BackgroundTransparency = 1 -- Mulai transparan, akan tween ke 0.2 + fullScreenFrame.Size = UDim2.new(1, 0, 1, 0) + fullScreenFrame.Position = UDim2.new(0, 0, 0, 0) + fullScreenFrame.Parent = screenGui + + -- Label utama untuk status (di tengah layar) + local statusLabel = Instance.new("TextLabel") + statusLabel.Name = "StatusLabel" + statusLabel.BackgroundTransparency = 1 + statusLabel.Text = "Just - Hub : Auto Parry" + statusLabel.TextColor3 = Color3.new(1, 1, 1) + statusLabel.Font = Enum.Font.SourceSansBold + statusLabel.TextSize = 36 + statusLabel.AnchorPoint = Vector2.new(0.5, 0.5) + statusLabel.Position = UDim2.new(0.5, 0, 0.35, 0) + -- Mulai dengan scale 0 (ukuran 0) untuk animasi scale-in + statusLabel.Size = UDim2.new(0, 0, 0, 0) + statusLabel.Parent = fullScreenFrame + + -- Label tambahan untuk informasi penting (misal: ping dan state) + local additionalInfoLabel = Instance.new("TextLabel") + additionalInfoLabel.Name = "AdditionalInfoLabel" + additionalInfoLabel.BackgroundTransparency = 1 + additionalInfoLabel.Text = "Loading info..." + additionalInfoLabel.TextColor3 = Color3.new(1, 1, 1) + additionalInfoLabel.Font = Enum.Font.SourceSans + additionalInfoLabel.TextSize = 24 + additionalInfoLabel.AnchorPoint = Vector2.new(0.5, 0.5) + additionalInfoLabel.Position = UDim2.new(0.5, 0, 0.5, 0) + additionalInfoLabel.Size = UDim2.new(0, 400, 0, 50) + additionalInfoLabel.Parent = fullScreenFrame + self.AdditionalInfoLabel = additionalInfoLabel + + -- Tombol Exit di bawah label tambahan + local exitButton = Instance.new("TextButton") + exitButton.Name = "ExitButton" + exitButton.Text = "Exit" + exitButton.TextColor3 = Color3.new(1, 1, 1) + exitButton.Font = Enum.Font.SourceSansBold + exitButton.TextSize = 28 + exitButton.BackgroundColor3 = Color3.new(0.3, 0.3, 0.3) + exitButton.Size = UDim2.new(0, 150, 0, 50) + exitButton.AnchorPoint = Vector2.new(0.5, 0) + exitButton.Position = UDim2.new(0.5, 0, 0.65, 0) + exitButton.Parent = fullScreenFrame + + exitButton.MouseButton1Click:Connect(function() + Player:Kick("Anda telah keluar dari game.") + end) + + self.FullScreenGui = screenGui + + -- Tween untuk animasi fade-in (background transparency dari 1 ke 0.2) + local tweenInfo = TweenInfo.new(1, Enum.EasingStyle.Sine, Enum.EasingDirection.Out) + local tweenFrame = TweenService:Create(fullScreenFrame, tweenInfo, {BackgroundTransparency = 0.2}) + tweenFrame:Play() + + -- Tween untuk animasi scale-in label utama (dari 0 ke ukuran asli) + local tweenLabel = TweenService:Create(statusLabel, tweenInfo, {Size = UDim2.new(0, 400, 0, 100)}) + tweenLabel:Play() + + -- Tween untuk animasi scale-in label informasi tambahan (sedikit scale-up) + local tweenInfoLabel = TweenService:Create(additionalInfoLabel, tweenInfo, {TextTransparency = 0}) + tweenInfoLabel:Play() +end + +-- Fungsi untuk mengirim event parry (klik mouse) dengan delay minimal +function AdvancedParryController:AttemptParry() + VirtualInputManager:SendMouseButtonEvent(0, 0, 0, true, game, 0) + task.delay(0.01, function() + VirtualInputManager:SendMouseButtonEvent(0, 0, 0, false, game, 0) + end) + self.LastParryTime = tick() +end + +-- Lakukan exponential smoothing pada percepatan (lebih agresif pada mode overpowered) +function AdvancedParryController:SmoothAcceleration(currentVelocity, dt) + local alpha = self.OverpowerMode and 0.8 or 0.4 + if not self.PreviousVelocity then + self.PreviousVelocity = currentVelocity + return Vector3.new(0, 0, 0) + end + local instantAccel = (currentVelocity - self.PreviousVelocity) / dt + self.SmoothedAccel = self.SmoothedAccel:Lerp(instantAccel, alpha) + self.PreviousVelocity = currentVelocity + return self.SmoothedAccel +end + +-- Prediksi waktu impact menggunakan rumus: s = vt + ½at² +function AdvancedParryController:PredictImpactTime(distance, currentVelocity, accel) + local v = currentVelocity.Magnitude + local a = accel.Magnitude + if abs(a) < 0.001 then + return (v > 0) and (distance / v) or math.huge + end + local discriminant = v * v + 2 * a * distance + if discriminant < 0 then return math.huge end + local t1 = (-v + sqrt(discriminant)) / a + local t2 = (-v - sqrt(discriminant)) / a + local t = math.huge + if t1 > 0 then t = t1 end + if t2 > 0 and t2 < t then t = t2 end + return t +end + +-- Prediksi posisi bola saat impact (asumsi percepatan konstan) +function AdvancedParryController:PredictImpactPosition(ballPos, currentVelocity, accel, t) + return ballPos + currentVelocity * t + 0.5 * accel * t * t +end + +-- Hitung sudut incoming: antara arah kecepatan bola dan vektor dari bola ke HRP +function AdvancedParryController:ComputeIncomingAngle(ballVelocity, HRP, ballPos) + local mag = ballVelocity.Magnitude + if mag <= 0 then return 180 end + local incomingVec = (HRP.Position - ballPos).Unit + local velDir = ballVelocity.Unit + local dot = clamp(velDir:Dot(incomingVec), -1, 1) + return deg(acos(dot)) +end + +-- Update state berdasarkan data bola, karakter, prediksi, dan validasi ekstra +function AdvancedParryController:UpdateState(dt) + local now = tick() + local ball, tImpactCandidate = self:SelectTargetBall() + if not ball then + self.State = STATE_IDLE + AdvancedLog("DEBUG", "Tidak ada bola target ditemukan.", nil) + self:InitializeBallListener() + return + end + self.CurrentBall = ball + + local character = self.Player.Character + local HRP = character and character:FindFirstChild("HumanoidRootPart") + if not HRP then + self.State = STATE_IDLE + AdvancedLog("WARN", "HumanoidRootPart tidak ditemukan.", nil) + return + end + + local success, ballVelocity = pcall(function() return ball.zoomies.VectorVelocity end) + if not success or typeof(ballVelocity) ~= "Vector3" then + AdvancedLog("ERROR", "VectorVelocity bola tidak valid.", {Error = tostring(ballVelocity)}) + return + end + + local speed = ballVelocity.Magnitude + local dynamicThreshold = self:ComputeDynamicThreshold() + local threshold = min(dynamicThreshold, MAX_IMPACT_TIME) + local distance = (HRP.Position - ball.Position).Magnitude + + -- Hitung percepatan, waktu impact, dan incoming angle + local prevVelocity = self.PreviousVelocity or ballVelocity + local accel = self:SmoothAcceleration(ballVelocity, dt) + local tImpact = self:PredictImpactTime(distance, ballVelocity, accel) + local incomingAngle = self:ComputeIncomingAngle(ballVelocity, HRP, ball.Position) + + -- Validasi target bola + local targetAttr = ball:GetAttribute("target") + if type(targetAttr) ~= "string" or targetAttr ~= self.Player.Name then + self.State = STATE_IDLE + AdvancedLog("DEBUG", "Bola tidak mengincar local player.", {Target = tostring(targetAttr)}) + return + end + + -- MODE OVERPOWER: Jika tImpact sudah sangat rendah, trigger parry langsung (bypass kondisi normal) + if self.OverpowerMode and tImpact <= threshold * 1.5 then + self:AttemptParry() + self.State = STATE_PARIED + AdvancedLog("ACTION", "OVERPOWER: Forced parry triggered.", {tImpact = tImpact, IncomingAngle = incomingAngle}) + end + + -- Jika bola berada dalam rentang 10-20 studs dengan incoming angle rendah + if distance >= 10 and distance <= 20 and incomingAngle < INCOMING_ANGLE_THRESHOLD then + self:AttemptParry() + self.State = STATE_PARIED + AdvancedLog("ACTION", "Direct parry triggered.", {Distance = distance, Angle = incomingAngle, tImpact = tImpact}) + end + + -- Mode SPAM: Jika bola sangat dekat (<= 10 studs) + if distance <= 10 then + if self.State ~= STATE_SPAM then + self.State = STATE_SPAM + AdvancedLog("INFO", "Entering SPAM mode.", {Distance = distance}) + end + local spamInterval = max(self.BaseSpamInterval * (1 - (speed / 100)), 0.03) + if now - self.LastSpamTime >= spamInterval then + self:AttemptParry() + self.LastSpamTime = now + AdvancedLog("ACTION", "Spam parry triggered.", {Distance = distance, SpamInterval = spamInterval}) + end + return + elseif self.State == STATE_SPAM then + self.State = STATE_TRACKING + AdvancedLog("INFO", "Exiting SPAM mode.", {Distance = distance}) + end + + -- Jika kecepatan bola sangat rendah, anggap bola berhenti + if speed < 0.1 then + if not self.BallStopped then + self.BallStopped = true + self.State = STATE_IDLE + AdvancedLog("INFO", "Ball stopped.", {Speed = speed}) + end + return + elseif self.BallStopped then + self.BallStopped = false + if self.State == STATE_IDLE then + self.State = STATE_TRACKING + AdvancedLog("INFO", "Ball resumed.", {Speed = speed}) + end + end + + -- Hitung curve angle (perubahan arah) antara frame sebelumnya dan saat ini + local curveAngle = 0 + if prevVelocity.Magnitude > 0 and ballVelocity.Magnitude > 0 then + local dot = clamp(prevVelocity.Unit:Dot(ballVelocity.Unit), -1, 1) + curveAngle = deg(acos(dot)) + end + + AdvancedLog("DEBUG", "Ball metrics updated.", { + Speed = speed, + CurveAngle = curveAngle, + Distance = distance, + tImpact = tImpact, + DynamicThreshold = dynamicThreshold, + IncomingAngle = incomingAngle, + State = self.State + }) + + if tImpact < dynamicThreshold * 0.5 and self.State ~= STATE_ANTICIPATING then + self.State = STATE_ANTICIPATING + AdvancedLog("INFO", "Entering ANTICIPATING.", {tImpact = tImpact}) + end + + if (self.State == STATE_TRACKING or self.State == STATE_ANTICIPATING) then + if tImpact <= threshold or (curveAngle >= 15 and tImpact <= MAX_IMPACT_TIME) then + self:AttemptParry() + self.State = STATE_PARIED + AdvancedLog("ACTION", "Parry triggered.", {tImpact = tImpact, CurveAngle = curveAngle}) + end + end + + if self.State == STATE_PARIED then + local effectiveCooldown = self.OverpowerMode and self.OverpowerCooldown or self.CooldownTime + if now - self.LastParryTime >= (threshold + effectiveCooldown) then + self.State = STATE_COOLDOWN + AdvancedLog("INFO", "Entering COOLDOWN.", {Elapsed = now - self.LastParryTime}) + end + end + + if self.State == STATE_COOLDOWN then + local effectiveCooldownFinish = self.OverpowerMode and (self.OverpowerCooldown + 0.05) or (self.CooldownTime + 0.5) + if now - self.LastParryTime >= (threshold + effectiveCooldownFinish) then + self.State = STATE_TRACKING + AdvancedLog("INFO", "Cooldown finished, resuming TRACKING.", nil) + end + end + + if curveAngle > 45 and tImpact < threshold then + self.State = STATE_TRACKING + AdvancedLog("INFO", "Reset parry due to sharp curve.", {CurveAngle = curveAngle}) + end + + if distance <= 0 then + AdvancedLog("WARN", "Invalid distance.", {Distance = distance}) + end + + -- Update label tambahan dengan informasi penting (misal: ping & state) + if self.AdditionalInfoLabel then + local pingMS = math.floor(self:GetLocalPing() * 1000) + self.AdditionalInfoLabel.Text = string.format("Ping: %d ms | State: %s", pingMS, self.State) + end +end + +------------------------------- +-- Main Update Loop +------------------------------- +local controller = AdvancedParryController.new() +controller:InitializeBallListener() +controller:InitializeGUI() + +RunService.Heartbeat:Connect(function(dt) + pcall(function() + controller:UpdateState(dt) + end) +end)