-- System by @thehandofvoid -- Modified by @jay_peaceee -- StarterPlayer/StarterPlayerScripts/FishingSystem -- ✅ NOW WITH GLOBAL CHAT NOTIFICATIONS FOR RARE FISH local Players = game:GetService("Players") local UIS = game:GetService("UserInputService") local RunService = game:GetService("RunService") local RepStorage = game:GetService("ReplicatedStorage"):WaitForChild("FishingSystem") local CS = game:GetService("CollectionService") local Debris = game:GetService("Debris") local StarterGui = game:GetService("StarterGui") -- 🎣 PENAMBAHAN UNTUK ANIMASI IKAN & CAMERA SHAKE local TweenService = game:GetService("TweenService") local player = Players.LocalPlayer local isMobile = UIS.TouchEnabled and not UIS.KeyboardEnabled -- ===== MODULES ===== local modulesFolder = RepStorage:WaitForChild("FishingModules") local FishingConfig = require(RepStorage:WaitForChild("FishingConfig")) local SoundManager = require(modulesFolder:WaitForChild("SoundManager")) local GUIManager = require(modulesFolder:WaitForChild("GUIManager")) local AnimationController = require(modulesFolder:WaitForChild("AnimationController")) local PowerBarSystem = require(modulesFolder:WaitForChild("PowerBarSystem")) local MinigameSystem = require(modulesFolder:WaitForChild("MinigameSystem")) local CastingSystem = require(modulesFolder:WaitForChild("CastingSystem")) -- ===== REMOTE EVENTS ===== local fishGiverEvent = RepStorage:WaitForChild("FishGiver") local castReplicationEvent = RepStorage:WaitForChild("CastReplication") local cleanupCastEvent = RepStorage:WaitForChild("CleanupCast") local showNotificationEvent = RepStorage:FindFirstChild("ShowNotification") -- 🆕 NEW: Global chat notification event local publishFishCatch = RepStorage:WaitForChild("PublishFishCatch") -- ===== STATE ===== local gameState = { isLocked = false, casted = false, sinker = nil, splash = nil, savedHookPosition = nil, waitTime = math.random(3, 6), fishingCaught = false, hasLanded = false, fishingInProgress = false, canCast = true, castingCooldown = false, cooldownTime = 0.3, activeFishingTask = nil, isAutoFishing = false } local playerPityData = FishingConfig.CreatePityTracker() local speedState = { originalWalkSpeed = 16, fishingWalkSpeed = 8, speedModified = false } local connections = {} local otherPlayersHooks = {} local character = player.CharacterAdded:Wait() local humanoid = character:WaitForChild("Humanoid") local playerGui = player:WaitForChild("PlayerGui") local camera = workspace.CurrentCamera -- ===== FORWARD DECLARATIONS ===== local updateMobileButtonText, onRodEquipped, onRodUnequipped, performCast -- ===== ROD MANAGER ===== local RodManager = {currentRod = nil, isEquipped = false} function RodManager:IsValidRod() return self.currentRod and self.currentRod.Parent and self.isEquipped and player.Character and self.currentRod:IsDescendantOf(player.Character) and self.currentRod:FindFirstChild("Part") end function RodManager:CleanupFishing() if not (gameState.casted or gameState.fishingInProgress) then return end gameState.casted, gameState.fishingInProgress = false, false gameState.canCast, gameState.castingCooldown = true, false gameState.activeFishingTask = nil if gameState.splash then gameState.splash:Destroy(); gameState.splash = nil end if gameState.sinker then gameState.sinker:Destroy(); gameState.sinker = nil end if MinigameSystem and MinigameSystem:IsActive() then MinigameSystem:ForceStop() end if PowerBarSystem and PowerBarSystem:IsCharging() then PowerBarSystem:StopCharging() end cleanupCastEvent:FireServer() if GUIManager and GUIManager.SlideFishingFrameOut then GUIManager:SlideFishingFrameOut(nil) end updateMobileButtonText() end function RodManager:OnRodEquipped(rod) if not rod or not rod.Parent then return end if self.currentRod and self.currentRod ~= rod then self:OnRodUnequipped() end self.currentRod, self.isEquipped = rod, true onRodEquipped(rod) end function RodManager:OnRodUnequipped() if not self.currentRod then return end self:CleanupFishing() self.currentRod, self.isEquipped = nil, false onRodUnequipped() end function RodManager:CheckEquippedRod(char) if not char then return end for _, tool in char:GetChildren() do if tool:IsA("Tool") and CS:HasTag(tool, "Rod") then if self.currentRod ~= tool then self:OnRodEquipped(tool) end return end end if self.isEquipped then self:OnRodUnequipped() end end function RodManager:SetupCharacterMonitoring(char) if not char then return end self:CheckEquippedRod(char) char.ChildAdded:Connect(function(child) if child:IsA("Tool") and CS:HasTag(child, "Rod") then self:OnRodEquipped(child) end end) char.ChildRemoved:Connect(function(child) if child:IsA("Tool") and CS:HasTag(child, "Rod") and self.currentRod == child then self:OnRodUnequipped() end end) local conn; conn = RunService.Heartbeat:Connect(function() if not char or not char.Parent then conn:Disconnect(); return end if self.isEquipped and not self:IsValidRod() then self:OnRodUnequipped() end self:CheckEquippedRod(char) end) end -- ===== HELPERS ===== local function getCurrentRodName() return RodManager.currentRod and RodManager.currentRod.Name end local function setSpeed(isFishing) local isLocked = gameState.isLocked -- Cek state penguncian baru if isLocked then -- Jika sedang dikunci, paksa WalkSpeed menjadi 0 if humanoid.WalkSpeed ~= 0 then humanoid.WalkSpeed = 0 speedState.speedModified = true end elseif isFishing and not speedState.speedModified then humanoid.WalkSpeed = speedState.fishingWalkSpeed speedState.speedModified = true elseif not (isFishing or isLocked) and speedState.speedModified then humanoid.WalkSpeed = speedState.originalWalkSpeed speedState.speedModified = false end end local function startSpeedMonitoring() if connections.speedCheck then connections.speedCheck:Disconnect() end connections.speedCheck = RunService.Heartbeat:Connect(function() local isFishing = gameState.casted or gameState.fishingInProgress or MinigameSystem:IsActive() or PowerBarSystem:IsCharging() or gameState.isLocked -- 🆕 Tambahkan isLocked ke kondisi isFishing setSpeed(isFishing and RodManager.isEquipped) end) end local function stopSpeedMonitoring() if connections.speedCheck then connections.speedCheck:Disconnect() connections.speedCheck = nil end setSpeed(false) end local function startCastingCooldown() gameState.castingCooldown, gameState.canCast = true, false updateMobileButtonText() task.delay(gameState.cooldownTime, function() gameState.castingCooldown, gameState.canCast = false, true updateMobileButtonText() end) end function updateMobileButtonText() if not GUIManager then return end local text = "CAST" if MinigameSystem:IsActive() then text = "KLIK!" elseif PowerBarSystem:IsCharging() then text = "LEMPAR!" elseif gameState.casted or gameState.fishingInProgress then text = "TARIK" elseif gameState.castingCooldown then text = "TUNGGU" end if gameState.isAutoFishing then if MinigameSystem:IsActive() or gameState.casted or gameState.fishingInProgress then text = "OTOMATIS" else text = "OTOMATIS" end end GUIManager:UpdateMobileButtonText(text) end local function selectFish() local rodName = getCurrentRodName() if not rodName then warn("⚠ No rod equipped!") return nil end local config = FishingConfig.GetRodConfig(rodName) local powerPercent = PowerBarSystem:GetCurrentPower() / 100 if gameState.isAutoFishing then powerPercent = 1.0 end local totalLuck = FishingConfig.CalculateTotalLuck(config.baseLuck, powerPercent) local selectedFish = FishingConfig.RollFish(playerPityData, rodName, totalLuck) if not selectedFish then warn("⚠ RollFish returned nil! Using fallback Common fish") for _, fish in ipairs(FishingConfig.FishTable) do if fish.rarity == "Common" then selectedFish = fish break end end if not selectedFish then return nil end end local fishWeight = FishingConfig.GenerateFishWeight(selectedFish, totalLuck, config.maxWeight) return selectedFish, fishWeight end -- ===== AUTO-FISHING LOOP ===== local autoFishConnection = nil local function autoCast() if not RodManager:IsValidRod() or gameState.casted or not character or not gameState.canCast or gameState.castingCooldown then return end gameState.canCast = false local power = math.random(90, 100) AnimationController:Play("WaitingAnimation", 0.1) SoundManager:Play("Cast", 0.5) task.wait(0.4) performCast(power) end local function startAutoFishLoop() if autoFishConnection then autoFishConnection:Disconnect() end autoFishConnection = RunService.Heartbeat:Connect(function() if gameState.isAutoFishing and RodManager.isEquipped and gameState.canCast and not gameState.casted and not gameState.fishingInProgress and not gameState.castingCooldown then autoCast() end end) end local function stopAutoFishLoop() if autoFishConnection then autoFishConnection:Disconnect() autoFishConnection = nil end end -- ===== INITIALIZATION ===== local function initializeSystems() SoundManager:Initialize() GUIManager:Initialize(player) local autoButton = GUIManager:GetElement("autoButton") if autoButton then autoButton.MouseButton1Click:Connect(function() gameState.isAutoFishing = not gameState.isAutoFishing GUIManager:UpdateAutoButton(gameState.isAutoFishing) if gameState.isAutoFishing then startAutoFishLoop() GUIManager:ShowNotification("Otomatis Menyala", 3, Color3.fromRGB(100, 255, 100)) if RodManager.isEquipped and gameState.canCast and not gameState.casted and not gameState.fishingInProgress and not gameState.castingCooldown then autoCast() end else stopAutoFishLoop() GUIManager:ShowNotification("Otomatis Mati", 3, Color3.fromRGB(255, 100, 100)) end end) end AnimationController:Initialize(humanoid) PowerBarSystem:Initialize({ barFrame = GUIManager:GetElement("barFrame"), fillFrame = GUIManager:GetElement("fillFrame"), luckMultiText = GUIManager:GetElement("luckMultiText") }) MinigameSystem:Initialize({ fishingFillFrame = GUIManager:GetElement("fishingFillFrame"), infoText = GUIManager:GetElement("infoText") }, nil) speedState.originalWalkSpeed = humanoid.WalkSpeed end initializeSystems() -- 🎣 FUNGSI BARU: CAMERA SHAKE (DIPERBAIKI DARI VERSI SEBELUMNYA) local function startCameraShakeHorizontal(duration) -- Pastikan kamera sudah siap local camera = workspace.CurrentCamera if not camera then workspace:GetPropertyChangedSignal("CurrentCamera"):Wait() camera = workspace.CurrentCamera end if not camera or camera.CameraSubject == nil then return end local startTime = tick() local shakeConnection; shakeConnection = RunService.RenderStepped:Connect(function() local elapsed = tick() - startTime local remaining = duration - elapsed if remaining <= 0 then -- Hentikan getaran setelah durasi selesai if shakeConnection then shakeConnection:Disconnect() end return end -- Ambil CFrame kamera saat ini (agar kamera tetap mengikuti pemain) local currentCFrame = camera.CFrame -- Mengatur kekuatan getaran (Shake Strength) -- Kekuatan akan berkurang dari 100% menjadi 0% seiring waktu local normalizedTime = remaining / duration local baseStrength = 5 -- Kekuatan maksimum awal local shakeStrength = baseStrength * normalizedTime -- Menghasilkan pergeseran CFrame acak HANYA pada sumbu X (Kanan-Kiri) -- math.random() * 2 - 1 menghasilkan angka antara -1 (Kiri) dan 1 (Kanan) local offsetX = (math.random() * 2 - 1) * shakeStrength * 0.3 -- Dikalikan 0.3 agar lebih terasa -- Offset Y dan Z dibuat 0 atau sangat kecil agar getaran fokus ke horizontal local offsetY = 0.01 * shakeStrength -- Sedikit offset vertikal mungkin diperlukan untuk "feel" local offsetZ = 0 -- Terapkan offset acak ke CFrame kamera saat ini camera.CFrame = currentCFrame * CFrame.new(offsetX, offsetY, offsetZ) end) end -- 🆕 FUNGSI BARU: SEQUENCE KAMERA JATUH IKAN -- 🆕 FUNGSI: SEQUENCE KAMERA JATUH IKAN (FIXED VIEW KE ATAS) local function fishDropCameraSequence(fishPart, duration) -- Pastikan kamera sudah siap if not camera or not fishPart or not fishPart.Parent then return end local initialCFrame = camera.CFrame -- Simpan posisi kamera awal local hrp = character:FindFirstChild("HumanoidRootPart") if not hrp then return end -- 1. Set CameraType ke Scriptable camera.CameraType = Enum.CameraType.Scriptable -- Menghitung Posisi Kamera Fixed: -- Kamera diposisikan DEKAT pemain (misal, 5 stud di depan pemain) -- dan DIHADAPKAN LURUS KE ATAS menuju titik 300 stud. local distanceInFront = 5 local fixedPosition = hrp.CFrame.Position + hrp.CFrame.LookVector * distanceInFront + Vector3.new(0, 5, 0) -- 5 stud di depan, 5 stud di atas tanah -- CFrame.lookAt(posisi_kamera, target_ikan_awal) -- Target awal ikan berada 300 stud di atas, 5 stud di depan pemain (dari fungsi showFishBouncingDropAnimation) local targetPosition = hrp.CFrame.Position + hrp.CFrame.LookVector * (-distanceInFront) + Vector3.new(0, 300, 0) local fixedCFrame = CFrame.lookAt(fixedPosition, targetPosition) -- Atur posisi kamera sekali saja camera.CFrame = fixedCFrame -- 2. Tunggu durasi yang diminta (8 detik) task.delay(duration, function() -- *** LOGIKA RUNSERVICE.HEARTBEAT DIHAPUS *** -- 3. Kembalikan CameraType ke Custom dengan transisi halus local transitionTweenInfo = TweenInfo.new(1, Enum.EasingStyle.Quad, Enum.EasingDirection.Out) if camera.CameraType == Enum.CameraType.Scriptable then local transitionTween = TweenService:Create(camera, transitionTweenInfo, {CFrame = initialCFrame}) transitionTween:Play() transitionTween.Completed:Wait() end camera.CameraType = Enum.CameraType.Custom end) end local function showFishBouncingDropAnimation(selectedFish) local fishModelName = selectedFish.name -- Mengambil model dari jalur yang benar (sesuai struktur Anda) local FishModelFolder = RepStorage:FindFirstChild("Assets") and RepStorage.Assets:FindFirstChild("FishModel") local fishTemplate = FishModelFolder and FishModelFolder:FindFirstChild(fishModelName) if not fishTemplate or not fishTemplate:IsA("Model") then warn("⚠ Model ikan tidak ditemukan untuk: " .. fishModelName .. ". Periksa jalur: ReplicatedStorage.FishingSystem.Assets.FishModel") return end local hrp = character:FindFirstChild("HumanoidRootPart") if not hrp then return end -- Kloning model dan setup dasar local fishClone = fishTemplate:Clone() fishClone.Parent = workspace local primaryPart = fishClone.PrimaryPart or fishClone:FindFirstChildOfClass("BasePart") if not primaryPart then warn("Model ikan tidak memiliki PrimaryPart yang ditetapkan.") fishClone:Destroy() return end -- 🔥 PERBAIKAN STUCK: Pastikan SEMUA bagian Unanchored agar model dapat digerakkan oleh Tween for _, part in fishClone:GetDescendants() do if part:IsA("BasePart") then part.CanCollide = false -- ⭐ SET SEMUA BAGIAN KE UNANCHORED = false part.Anchored = false end end -- Parameter Animasi local TweenService = game:GetService("TweenService") local horizontalDistance = 5--Jarak di depan pemain -- 🔥 PERUBAHAN UTAMA: DURASI JADI 15.0 DETIK local startHeight = 300 -- Ketinggian Awal Jatuh: (300 STUD) local groundHeight = 0.5 -- Ketinggian 'Tanah' (1.5 STUD DARI BAWAH) local dropDuration = 20.0 -- ⏳ DURASI JATUH UTAMA (Slow-Mo 15.0 DETIK) local durasicamera = 2.0 -- CFrame Awal (Start): Di udara local startCFrame = hrp.CFrame * CFrame.new(0, startHeight, -horizontalDistance) * CFrame.Angles(math.rad(-60), math.rad(45), math.rad(15)) -- CFrame Tengah (Ground): Posisi kontak pertama (Posisi akhir) local groundCFrame = hrp.CFrame * CFrame.new(0, groundHeight, -horizontalDistance) * CFrame.Angles(0, 0, 0) -- Mengatur posisi awal menggunakan SetPrimaryPartCFrame() fishClone:SetPrimaryPartCFrame(startCFrame) -- PANGGIL FUNGSI KAMERA SEBELUM TWEEN JATUH DIMULAI -- Kamera juga akan berjalan selama 15.0 detik fishDropCameraSequence(primaryPart, durasicamera) local dropTweenInfo = TweenInfo.new( dropDuration, Enum.EasingStyle.Quart, Enum.EasingDirection.In ) local dropTween = TweenService:Create(primaryPart, dropTweenInfo, { CFrame = groundCFrame, Rotation = Vector3.new(0, 360 * 2, 0) -- Rotasi 720 derajat selama 15 detik }) -- Jalankan Animasi dropTween:Play() -- 🔥 PENTING: Anchor kembali PrimaryPart SETELAH tween selesai dropTween.Completed:Connect(function(state) if state == Enum.PlaybackState.Completed then primaryPart.Anchored = true end end) -- Ikan akan hilang setelah semua animasi selesai Debris:AddItem(fishClone, dropDuration + 1.0) end -- 🎣 AKHIR FUNGSI CAMERA SHAKE -- 🎣 PENAMBAHAN UNTUK ANIMASI IKAN: Fungsi untuk menampilkan model ikan local function showFishCatchAnimation(selectedFish) local fishModelName = selectedFish.name -- Mengambil model dari jalur yang benar (sesuai struktur Anda) local FishModelFolder = RepStorage:FindFirstChild("Assets") and RepStorage.Assets:FindFirstChild("FishModel") local fishTemplate = FishModelFolder and FishModelFolder:FindFirstChild(fishModelName) if not fishTemplate or not fishTemplate:IsA("Model") then warn("⚠ Model ikan tidak ditemukan untuk: " .. fishModelName .. ". Periksa jalur: ReplicatedStorage.FishingSystem.Assets.FishModel") return end local hrp = character:FindFirstChild("HumanoidRootPart") if not hrp then return end local fishClone = fishTemplate:Clone() fishClone.Parent = workspace local primaryPart = fishClone.PrimaryPart or fishClone:FindFirstChildOfClass("BasePart") if not primaryPart then warn("Model ikan tidak memiliki PrimaryPart yang ditetapkan.") fishClone:Destroy() return end -- Setup Awal primaryPart.Anchored = true -- Jarak di depan dan belakang pemain local distance = 10 -- Ketinggian lompatan local height = 10 -- Durasi total animasi melintas local duration = 0.8 -- CFrame Awal: DEPAN PEMAIN (x=0, y=1.5, z=-distance). Diputar menghadap pemain (180 derajat). local startCFrame = hrp.CFrame * CFrame.new(0, 1.5, -distance) * CFrame.Angles(0, math.pi, 0) -- CFrame Tengah (Puncak Lompatan): DI ATAS PEMAIN (x=0, y=1.5+height, z=0). Diputar menghadap depan (90 derajat). local peakCFrame = hrp.CFrame * CFrame.new(0, 1.5 + height, 0) * CFrame.Angles(0, math.pi / 2, 0) -- CFrame Akhir: BELAKANG PEMAIN (x=0, y=1.5, z=distance). Diputar menghadap belakang (0 derajat). local endCFrame = hrp.CFrame * CFrame.new(0, 1.5, distance) * CFrame.Angles(0, 0, 0) fishClone:SetPrimaryPartCFrame(startCFrame) -- 1. TWEEN MELOMPAT KE ATAS (DEPAN -> ATAS) local tweenInfo1 = TweenInfo.new( duration / 2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out ) -- Tween 1 bergerak dari Depan (startCFrame) ke Puncak (peakCFrame) local tween1 = TweenService:Create(primaryPart, tweenInfo1, {CFrame = peakCFrame}) -- 2. TWEEN TURUN KE BELAKANG (ATAS -> BELAKANG) local tweenInfo2 = TweenInfo.new( duration / 2, Enum.EasingStyle.Quad, Enum.EasingDirection.In ) -- Tween 2 bergerak dari Puncak (peakCFrame) ke Belakang (endCFrame) local tween2 = TweenService:Create(primaryPart, tweenInfo2, {CFrame = endCFrame}) -- Jalankan Tween secara berurutan tween1:Play() tween1.Completed:Connect(function(state) if state == Enum.PlaybackState.Completed then tween2:Play() end end) -- Hancurkan ikan setelah durasi total Debris:AddItem(fishClone, duration + 0.1) end -- 🎣 AKHIR PENAMBAHAN FUNGSI local function startCameraShakeHorizontal1(duration, strengthOverride) -- Pastikan kamera sudah siap local camera = workspace.CurrentCamera if not camera then workspace:GetPropertyChangedSignal("CurrentCamera"):Wait() camera = workspace.CurrentCamera end if not camera or camera.CameraSubject == nil then return end local startTime = tick() local shakeConnection; -- 🔥 PERUBAHAN: Tentukan Kekuatan Dasar. Gunakan override, jika tidak ada, pakai 15 (SANGAT GANAS) local baseStrength = strengthOverride or 15 shakeConnection = RunService.RenderStepped:Connect(function() -- Hentikan koneksi shake yang mungkin sedang berjalan sebelumnya (Penting untuk mencegah double-shake) if shakeConnection and not shakeConnection.Connected then return end local elapsed = tick() - startTime local remaining = duration - elapsed if remaining <= 0 then -- Hentikan getaran setelah durasi selesai if shakeConnection then shakeConnection:Disconnect() end return end -- Ambil CFrame kamera saat ini (agar kamera tetap mengikuti pemain) local currentCFrame = camera.CFrame -- Mengatur kekuatan getaran (Shake Strength) -- Kekuatan akan berkurang dari 100% menjadi 0% seiring waktu local normalizedTime = remaining / duration -- 🔥 Menggunakan baseStrength baru (15) untuk meningkatkan amplitudo local shakeStrength = baseStrength * normalizedTime -- Menghasilkan pergeseran CFrame acak HANYA pada sumbu X (Kanan-Kiri) -- math.random() * 2 - 1 menghasilkan angka antara -1 (Kiri) dan 1 (Kanan) local offsetX = (math.random() * 2 - 1) * shakeStrength * 0.3 -- Multiplier 0.3 menjaga frekuensi tetap, tetapi amplitudo mengikuti shakeStrength. -- Offset Y dan Z dibuat 0 atau sangat kecil agar getaran fokus ke horizontal local offsetY = 0.01 * shakeStrength local offsetZ = 0 -- Terapkan offset acak ke CFrame kamera saat ini camera.CFrame = currentCFrame * CFrame.new(offsetX, offsetY, offsetZ) end) end -- ======================================================= -- 🛠️ PERBAIKAN FUNGSI getFishImageId -- Mengambil TextureId dari Model Ikan di RepStorage.FishingSystem.Assets.Fish -- ======================================================= -- ======================================================= -- 🛠️ FUNGSI getFishImageId YANG SUDAH DIPERBAIKI -- Mengambil TextureId dari properti Model/Tool (sesuai gambar) -- ======================================================= local TweenService = game:GetService("TweenService") local TWEEN_DURATION = 0.8 -- Durasi animasi slide local VISIBLE_DURATION = 2.5 -- Durasi notifikasi terlihat di layar -- ======================================================= -- 🆕 ARRAY MAP UNTUK MENYIMPAN ID GAMBAR IKAN -- ======================================================= local FALLBACK_IMAGE_ID = "rbxassetid://98117815811053" -- ID Gambar Default/Fallback local FishTextureMap = { ["Ancient Relic Crocodile"] = "rbxassetid://131025252592514", ["Ancient Whale"] = "rbxassetid://106712272327597", ["Arapaima Fish"] = FALLBACK_IMAGE_ID, ["Barracuda Fish"] = FALLBACK_IMAGE_ID, ["Blackcap Basslet"] = "rbxassetid://95659897738388", ["Blue Fish"] = "rbxassetid://956598977383881", ["Boar Fish"] = "rbxassetid://114009571858522", ["Bone Fish"] = FALLBACK_IMAGE_ID, ["Buaya Hutan"] = FALLBACK_IMAGE_ID, ["Chines Blue Fish"] = FALLBACK_IMAGE_ID, ["Chines Fish"] = FALLBACK_IMAGE_ID, ["Chines Green Fish"] = FALLBACK_IMAGE_ID, ["Cleo Fish"] = FALLBACK_IMAGE_ID, ["Cobia"] = FALLBACK_IMAGE_ID, ["Crimsom Ray"] = FALLBACK_IMAGE_ID, ["Dead Scary Clownfish"] = "rbxassetid://130326949380133", ["Dead Spooky Koi Fish"] = "rbxassetid://94754294966347", ["Deep Fish"] = FALLBACK_IMAGE_ID, ["El Maja"] = FALLBACK_IMAGE_ID, ["Fangtooth"] = "rbxassetid://82285034746187", ["Fish Black"] = FALLBACK_IMAGE_ID, ["Fish Lake"] = FALLBACK_IMAGE_ID, ["Fish Tipis"] = FALLBACK_IMAGE_ID, ["Fish benrtol"] = FALLBACK_IMAGE_ID, ["Fish gead"] = FALLBACK_IMAGE_ID, ["Freshwater Piranha"] = "rbxassetid://133381366802339", ["Genetik Fish"] = FALLBACK_IMAGE_ID, ["Geo Fish"] = FALLBACK_IMAGE_ID, ["Ghost Fish"] = FALLBACK_IMAGE_ID, ["Ghost Ray"] = FALLBACK_IMAGE_ID, ["GoldenTrout"] = FALLBACK_IMAGE_ID, ["Goldfish"] = "rbxassetid://79451780989889", ["Green Fish"] = FALLBACK_IMAGE_ID, ["Hermit Crab"] = "rbxassetid://84444153694943", ["Jellyfish"] = "rbxassetid://115477999636961", ["King Crab"] = "rbxassetid://136951139611035", ["KingJally Strong"] = "rbxassetid://104757933481327", ["Kraken"] = "rbxassetid://80927639907406", ["Fish"] = FALLBACK_IMAGE_ID, ["Light Dolphin"] = "rbxassetid://99527607304877", ["Lion Fish"] = "rbxassetid://92595165502684", ["Loving Shark"] = "rbxassetid://119173750281399", ["Luminous Fish"] = "rbxassetid://78374908088019", ["Mega Hunt"] = "rbxassetid://126066407227328", ["Monster Shark"] = "rbxassetid://109551787474599", ["Morning Star"] = FALLBACK_IMAGE_ID, ["Mujaer"] = FALLBACK_IMAGE_ID, ["Naga"] = "rbxassetid://108665275325646", ["Nemo"] = FALLBACK_IMAGE_ID, ["Nila Fish"] = FALLBACK_IMAGE_ID, ["Pink Dolphin"] = "rbxassetid://86256600614394", ["Piranha Fish"] = FALLBACK_IMAGE_ID, ["Plasma Shark"] = "rbxassetid://88847410678758", ["Puffy Blowhog"] = FALLBACK_IMAGE_ID, ["Pumpkin Carved Shark"] = FALLBACK_IMAGE_ID, -- Entri BARU dari gambar terakhir: ["Queen Crab"] = FALLBACK_IMAGE_ID, ["Rock Fish"] = FALLBACK_IMAGE_ID, ["Roster Fish"] = FALLBACK_IMAGE_ID, ["Sapu Sapu Goib"] = FALLBACK_IMAGE_ID, ["Shark"] = FALLBACK_IMAGE_ID, ["Shark Bone"] = FALLBACK_IMAGE_ID, ["Sotong"] = FALLBACK_IMAGE_ID, ["Totol"] = FALLBACK_IMAGE_ID, ["Udang"] = FALLBACK_IMAGE_ID, ["Ular kadut"] = FALLBACK_IMAGE_ID, ["Wraithfin Abyssal"] = FALLBACK_IMAGE_ID, ["Yellow Fish"] = FALLBACK_IMAGE_ID, ["Zombie Shark"] = FALLBACK_IMAGE_ID, ["purple Kraken"] = FALLBACK_IMAGE_ID, } -- ======================================================= -- 🛠️ FUNGSI getFishImageId YANG DIPERBAIKI (Penanganan Warning) -- ======================================================= local function getFishImageId(fishName) local imageId = FishTextureMap[fishName] if imageId and typeof(imageId) == "string" then return imageId end -- Jika key tidak ada sama sekali atau nil/salah tipe warn("TextureId untuk '" .. fishName .. "' tidak ditemukan di FishTextureMap (Key Missing atau Tipe Salah). Menggunakan fallback.") return FALLBACK_IMAGE_ID end -- ======================================================= -- 🐟 FUNGSI UTAMA showFishCatchNotification -- ======================================================= local function showFishCatchNotification(selectedFish, fishWeight) -- Catatan: 'player', 'GUIManager', dan 'FishingConfig' harus didefinisikan -- dan tersedia di dalam scope skrip Anda. local notificationGui = player:FindFirstChild("PlayerGui"):FindFirstChild("Small Notification") if not notificationGui or not notificationGui:IsA("ScreenGui") then warn("Small Notification GUI tidak ditemukan. Melakukan fallback ke notifikasi standar.") if GUIManager and FishingConfig then GUIManager:ShowNotification( string.format("Mendapatkan %s %.1fkg", selectedFish.name, fishWeight), 4, FishingConfig.GetRarityColor(selectedFish.rarity) ) end return end -- Struktur GUI berdasarkan gambar: Small Notification > Display > ... local display = notificationGui:FindFirstChild("Display") if not display then warn("Display frame tidak ditemukan."); return end local container = display:FindFirstChild("Container") local itemName = container and container:FindFirstChild("ItemName") local rarityText = container and container:FindFirstChild("Rarity") local vectorFrame = display:FindFirstChild("VectorFrame") local raysEffect = vectorFrame and vectorFrame:FindFirstChild("Rays") local vectorElement = vectorFrame and vectorFrame:FindFirstChild("Vector") -- Logika Pencarian Gambar Ikan (FishImageLabel) local fishImageLabel = nil local KnownChildrenNames = { "Container", "NewFrame", "VectorFrame", "UIScale" } fishImageLabel = display:FindFirstChild("FishImage") or display:FindFirstChild("Icon") if not fishImageLabel then for _, child in display:GetChildren() do if child:IsA("ImageLabel") then local isKnownStructure = false for _, name in ipairs(KnownChildrenNames) do if child.Name == name then isKnownStructure = true break end end if not isKnownStructure and child.Name ~= "Rays" and child.Name ~= "Vector" then fishImageLabel = child break end end end end if not fishImageLabel then local NewFrame = display:FindFirstChild("NewFrame") if NewFrame then fishImageLabel = NewFrame:FindFirstChildOfClass("ImageLabel") end end -- Akhir Logika Pencarian local rarityColor = FishingConfig.GetRarityColor(selectedFish.rarity) local targetPosition = UDim2.new(0.5, 0, 0.05, 0) local hiddenPosition = UDim2.new(0.5, 0, -0.5, 0) local fadeInInfo = TweenInfo.new(TWEEN_DURATION, Enum.EasingStyle.Quad, Enum.EasingDirection.Out) local fadeOutInfo = TweenInfo.new(TWEEN_DURATION, Enum.EasingStyle.Quad, Enum.EasingDirection.In) -- 1. Update Konten & Set Posisi Awal (Hidden) local imageId = getFishImageId(selectedFish.name) if fishImageLabel and fishImageLabel:IsA("ImageLabel") then fishImageLabel.Image = imageId if imageId == FALLBACK_IMAGE_ID then warn("⚠ ID Gambar untuk ikan '" .. selectedFish.name .. "' menggunakan ID Fallback pada FishImageLabel.") end else warn("⚠ FINAL ERROR: ImageLabel Ikan (FishImageLabel/Icon) tidak ditemukan.") end if vectorElement and vectorElement:IsA("ImageLabel") then vectorElement.Image = imageId else warn("⚠ FINAL ERROR: ImageLabel 'Vector' tidak ditemukan di VectorFrame.") end if itemName then itemName.Text = selectedFish.name .. string.format(" (%.1fkg)", fishWeight) itemName.TextColor3 = rarityColor end if rarityText then rarityText.Text = selectedFish.rarity rarityText.TextColor3 = rarityColor end if raysEffect and raysEffect:IsA("ImageLabel") then raysEffect.ImageColor3 = rarityColor end -- Set posisi awal di luar layar display.Position = hiddenPosition -- 2. AKTIFKAN GUI notificationGui.Enabled = true -- 3. JALANKAN SEMUA ANIMASI DI THREAD TERPISAH (task.spawn) -- Fungsi showFishCatchNotification akan selesai segera. task.spawn(function() -- 3. ⬇️ ANIMASI SLIDE MASUK local slideInTween = TweenService:Create(display, fadeInInfo, {Position = targetPosition}) slideInTween:Play() slideInTween.Completed:Wait() -- 4. ✨ ANIMASI GOYANGAN (SHAKE) ✨ local shakeOffset = 3 local shakeDuration = 0.05 local originalXOffset = display.Position.X.Offset local shakeTweenInfo = TweenInfo.new(shakeDuration, Enum.EasingStyle.Sine, Enum.EasingDirection.Out) for i = 1, 2 do local shakeRightTween = TweenService:Create(display, shakeTweenInfo, { Position = UDim2.new(display.Position.X.Scale, originalXOffset + shakeOffset, display.Position.Y.Scale, display.Position.Y.Offset) }) shakeRightTween:Play() task.wait(shakeDuration) local shakeLeftTween = TweenService:Create(display, shakeTweenInfo, { Position = UDim2.new(display.Position.X.Scale, originalXOffset - shakeOffset, display.Position.Y.Scale, display.Position.Y.Offset) }) shakeLeftTween:Play() task.wait(shakeDuration) end local returnTween = TweenService:Create(display, shakeTweenInfo, { Position = UDim2.new(display.Position.X.Scale, originalXOffset, display.Position.Y.Scale, display.Position.Y.Offset) }) returnTween:Play() returnTween.Completed:Wait() -- 5. TUNGGU DURASI TERLIHAT task.wait(VISIBLE_DURATION) -- 6. ⬆️ ANIMASI SLIDE KELUAR local slideOutTween = TweenService:Create(display, fadeOutInfo, {Position = hiddenPosition}) slideOutTween:Play() slideOutTween.Completed:Wait() -- 7. NONAKTIFKAN GUI notificationGui.Enabled = false end) end -- 🎣 AKHIR PENAMBAHAN FUNGSI -- ===== MINIGAME CALLBACKS ===== MinigameSystem:SetCallbacks( function() -- Success gameState.fishingCaught = true updateMobileButtonText() if RodManager.isEquipped and RodManager.currentRod then AnimationController:TransitionTo("EquippedAnimation", 0.3) end task.delay(0.3, function() local selectedFish, fishWeight = selectFish() -- 🔥 GUARD CLAUSE BARU 🔥 -- Jika penangkapan tidak menghasilkan ikan, jalankan cooldown normal dan keluar. if not selectedFish then startCastingCooldown() return end -- 🔥 AKHIR GUARD CLAUSE 🔥 if selectedFish then -- Daftar raritas yang memicu Lock (Jeda 4 detik, Camera Shake, PartSecret) local rarityToLock = { ["Secret"] = true, } -- Daftar raritas yang memicu AUTO-STOP local rarityToAutoStop = { } -- Daftar raritas yang menggunakan animasi JATUH & MANTUL local rarityForBounce = { ["Mitos"] = true, -- ⭐ HANYA SECRET YANG MEMICU JATUH/SLOWMO CAMERA ⭐ -- Hapus Epic jika Anda tidak ingin Epic jatuh/mantul } -- 🔥 PERHATIAN: -- Pastikan durasi di fungsi showFishBouncingDropAnimation juga diatur ke 7.0 detik -- agar animasi dan penguncian kamera (Camera Shake) sinkron. -- Tetapkan durasi lock yang konsisten. Saya menggunakan 7.0 detik total sesuai permintaan Anda. local lockDuration = 4 -- Durasi Lock, Camera Shake, dan PartSecret -- Cek apakah ikan yang didapat memicu Lock local requiresLock = rarityToLock[selectedFish.rarity] local SLOW_MO_LOCK_DURATION = 2.0 local finalLockDuration = 2.0 -- Default: Durasi kunci standar (non-slow-mo) -- 🎣 LOGIKA PEMANGGILAN ANIMASI IKAN (PERBAIKAN BUGS) if rarityForBounce[selectedFish.rarity] then -- Kasus 1: Ikan Slow-Mo (LOCK 7.0 detik) showFishBouncingDropAnimation(selectedFish) -- 🔥 PERBAIKAN: Gunakan durasi kunci 7.0 detik. finalLockDuration = SLOW_MO_LOCK_DURATION -- 1. Terapkan Lock Segera gameState.canCast = false gameState.castingCooldown = true gameState.isLocked = true updateMobileButtonText() -- Panggil EFEK Lock (Camera Shake, Sound, PartSecret) menggunakan 7.0 detik startCameraShakeHorizontal(finalLockDuration) -- MUNCULKAN EFEK SUARA DAN PARTSECRET DI SINI (kode Anda yang terpotong) -- ... -- 2. Aktifkan Kembali Setelah Lock Selesai task.delay(finalLockDuration, function() -- Logika UNLOCK dilakukan tepat setelah finalLockDuration (7.0 detik) SoundManager:Play("Success", 0.6) showFishCatchNotification(selectedFish, fishWeight) gameState.canCast = true gameState.castingCooldown = false gameState.isLocked = false updateMobileButtonText() -- Cek apakah auto-fishing harus dimatikan (HANYA Mitos/Secret) if gameState.isAutoFishing and rarityToAutoStop[selectedFish.rarity] then gameState.isAutoFishing = false stopAutoFishLoop() GUIManager:ShowNotification("Auto-Fishing Mati (Ikan Langka)", 3, Color3.fromRGB(255, 150, 50)) end updateMobileButtonText() -- Jika auto-fishing aktif, panggil autoCast if gameState.isAutoFishing then task.delay(0.5, autoCast) end end) else if rarityToLock[selectedFish.rarity] then -- Kasus 2: Ikan Non-Slow-Mo (Melintas) -- 🆕 LOGIKA PENGUNCIAN MEMANCING (LOCK) HANYA UNTUK RARITAS TINGGI if requiresLock then -- 1. Terapkan Lock Segera (Menghentikan Semua Casting) gameState.canCast = false gameState.castingCooldown = true gameState.isLocked = true updateMobileButtonText() -- 🔥 PERBAIKAN: Menggunakan fungsi horizontal yang benar startCameraShakeHorizontal(lockDuration) -- MENCARI FOLDER ASSETS local AssetsFolder = RepStorage:FindFirstChild("Assets") -- MUNCULKAN EFEK SUARA BARU local SoundFolder = AssetsFolder and AssetsFolder:FindFirstChild("Sound") local rareSoundTemplate = SoundFolder and SoundFolder:FindFirstChild("RareCatchSound") -- Digunakan untuk Tahap 2 local listrikSoundTemplate = SoundFolder and SoundFolder:FindFirstChild("Listrik") -- Digunakan untuk Tahap 1 (Asumsi: 'Listrik' adalah nama template) local EffectFolder = AssetsFolder and AssetsFolder:FindFirstChild("Effect") local partSecretTemplate = EffectFolder and EffectFolder:FindFirstChild("PartSecret") -- Digunakan untuk Tahap 2 local partSecretTemplate1 = EffectFolder and EffectFolder:FindFirstChild("PartSecret1") -- Digunakan untuk Tahap 1 -- 🔥 Durasi Tahap (Perhatikan: lockDuration = 4.0 detik) local stageDuration = lockDuration -- 4.0 detik local totalLockDuration = lockDuration * 2 -- 8.0 detik -- ⭐ FUNGSI PEMBANTU UNTUK MEMPOSISIKAN EFEK (Disederhanakan) local function positionEffect(effectClone, template, duration) local hrp = character:FindFirstChild("HumanoidRootPart") if not hrp then return end local distanceInFront = 12 local distanceRight = 0 local heightAboveWater = 1.0 local hrpCFrame = hrp.CFrame local projectedPosition = (hrpCFrame * CFrame.new(distanceRight, 0, -distanceInFront)).Position local finalY = projectedPosition.Y local waterY = CastingSystem:GetWaterSurfaceY(projectedPosition.X, projectedPosition.Z) if waterY then finalY = waterY + heightAboveWater end local finalPosition = Vector3.new(projectedPosition.X, finalY, projectedPosition.Z) -- Menggunakan rotasi pemain yang dimodifikasi, kemudian digeser Z 90 derajat lokal local desiredCFrame = CFrame.new(projectedPosition.X, finalY, projectedPosition.Z) * (hrpCFrame - hrpCFrame.Position) desiredCFrame = desiredCFrame * CFrame.Angles(0, 0, math.rad(90)) if effectClone:IsA("Model") and effectClone.PrimaryPart then effectClone:SetPrimaryPartCFrame(desiredCFrame) elseif effectClone:IsA("BasePart") then effectClone.CFrame = desiredCFrame end if effectClone:IsA("Model") then for _, child in effectClone:GetChildren() do if child:IsA("BasePart") then child.CanCollide = false child.Anchored = true end end end Debris:AddItem(effectClone, duration) end local hrp1 = character:FindFirstChild("HumanoidRootPart") -- ---------------------------------------------------- -- 🔥 TAHAP 1: PARTSECRET1 + SOUND LISTRIK (0 - 4.0 detik) -- ---------------------------------------------------- if partSecretTemplate1 and (partSecretTemplate1:IsA("BasePart") or partSecretTemplate1:IsA("Model")) then if hrp1 then -- SOUND LISTRIK if listrikSoundTemplate and listrikSoundTemplate:IsA("Sound") then local Listrik = listrikSoundTemplate:Clone() Listrik.Parent = workspace Listrik:Play() Debris:AddItem(Listrik, totalLockDuration) -- Sound berjalan total 8.0s else warn("⚠ Efek Suara Listrik tidak ditemukan!") end -- EFEK PARTSECRET1 local effectClone1 = partSecretTemplate1:Clone() effectClone1.Parent = workspace -- Efek pertama ini akan bertahan selama durasi total (8.0s) positionEffect(effectClone1, partSecretTemplate1, totalLockDuration) end else warn("⚠️ PartSecret1 tidak ditemukan di jalur yang benar.") end -- ---------------------------------------------------- -- 🔥 TAHAP 2: PARTSECRET + SOUND RARECATCH (4.0 - 8.0 detik) -- ---------------------------------------------------- task.delay(stageDuration, function() -- Pastikan PartSecret yang lama tidak hancur (sesuai permintaan "ditimpah jangan diganti") if partSecretTemplate and (partSecretTemplate:IsA("BasePart") or partSecretTemplate:IsA("Model")) then local hrp = character:FindFirstChild("HumanoidRootPart") startCameraShakeHorizontal1(lockDuration) if hrp then -- SOUND RARECATCH (Dimulai pada 4.0 detik, berjalan 4.0 detik) if rareSoundTemplate and rareSoundTemplate:IsA("Sound") then local RareCatch = rareSoundTemplate:Clone() RareCatch.Parent = workspace RareCatch:Play() Debris:AddItem(RareCatch, stageDuration) else warn("⚠ Efek Suara RareCatchSound tidak ditemukan!") end -- EFEK PARTSECRET local effectClone = partSecretTemplate:Clone() effectClone.Parent = workspace -- Efek kedua ini akan bertahan selama durasi stage (4.0s) positionEffect(effectClone, partSecretTemplate, stageDuration) end -- End if hrp else warn("⚠️ PartSecret tidak ditemukan di jalur yang benar.") end end) -- ---------------------------------------------------- -- 2. LOGIKA UNLOCK/COOLDOWN TOTAL (8.0 detik) -- ---------------------------------------------------- task.delay(totalLockDuration, function() -- 🔥 Perbaikan: Tambahkan jeda singkat untuk memastikan semua efek dan animasi selesai. task.wait(0.1) SoundManager:Play("Success", 0.6) showFishCatchNotification(selectedFish, fishWeight) gameState.canCast = true gameState.castingCooldown = false gameState.isLocked = false -- Cek apakah auto-fishing harus dimatikan (HANYA Mitos/Secret) if gameState.isAutoFishing and rarityToAutoStop[selectedFish.rarity] then gameState.isAutoFishing = false stopAutoFishLoop() GUIManager:ShowNotification("Auto-Fishing Mati (Ikan Langka)", 3, Color3.fromRGB(255, 150, 50)) end updateMobileButtonText() -- 🔥 Jika auto-fishing aktif, panggil autoCast di sini (di luar loop Heartbeat) if gameState.isAutoFishing then task.delay(0.5, autoCast) -- Delay sedikit untuk memastikan loop Heartbeat tidak race condition end end) else -- Jika tidak ada Lock (Common/Rare) startCastingCooldown() end else showFishCatchAnimation(selectedFish) SoundManager:Play("Success", 0.6) showFishCatchNotification(selectedFish, fishWeight) gameState.canCast = true gameState.castingCooldown = false -- Cek apakah auto-fishing harus dimatikan (HANYA Mitos/Secret) if gameState.isAutoFishing and rarityToAutoStop[selectedFish.rarity] then gameState.isAutoFishing = false stopAutoFishLoop() GUIManager:ShowNotification("Auto-Fishing Mati (Ikan Langka)", 3, Color3.fromRGB(255, 150, 50)) end updateMobileButtonText() -- 🔥 Jika auto-fishing aktif, panggil autoCast di sini (di luar loop Heartbeat) if gameState.isAutoFishing then task.delay(0.5, autoCast) -- Delay sedikit untuk memastikan loop Heartbeat tidak race condition end end end -- 🎣 AKHIR LOGIKA PEMANGGILAN ANIMASI IKAN -- ... (lanjutan kode) -- 🆕 AKHIR LOGIKA PENGUNCIAN MEMANCING -- Send fish to server fishGiverEvent:FireServer({ name = selectedFish.name, weight = fishWeight, rarity = selectedFish.rarity, hookPosition = gameState.savedHookPosition }) -- Publish rare fish catches to global chat (tetap hanya untuk Epic+) if selectedFish.rarity == "Epic" or selectedFish.rarity == "Legendary" or selectedFish.rarity == "Mitos" or selectedFish.rarity == "Secret" then publishFishCatch:FireServer(selectedFish.name, fishWeight, selectedFish.rarity) end gameState.savedHookPosition = nil end end) end, function() -- Fail gameState.savedHookPosition = nil if RodManager.isEquipped and RodManager.currentRod then AnimationController:TransitionTo("EquippedAnimation", 0.3) end GUIManager:ShowNotification("Ikan itu lolos!", 2, Color3.fromRGB(255, 100, 100)) MinigameSystem:Stop() startCastingCooldown() end ) -- ===== HOTKEY BLOCKER ===== UIS.InputBegan:Connect(function(input, processed) if processed or not MinigameSystem:IsActive() then return end local blocked = {Enum.KeyCode.One, Enum.KeyCode.Two, Enum.KeyCode.Three, Enum.KeyCode.Four, Enum.KeyCode.Five, Enum.KeyCode.Six, Enum.KeyCode.Seven, Enum.KeyCode.Eight, Enum.KeyCode.Nine, Enum.KeyCode.Backspace} for _, key in blocked do if input.KeyCode == key then return end end end) -- ===== FISHING LOOP ===== local function runFishingSequence(taskId, splash) local checks = { function() return gameState.activeFishingTask == taskId end, function() return RodManager.isEquipped and RodManager.currentRod end, function() return RodManager:IsValidRod() end, function() return gameState.casted and gameState.sinker end } local function validate() for _, check in checks do if not check() then return false end end return true end local function cleanup() if splash then splash:Destroy() end gameState.splash = nil end if not validate() then cleanup(); return false end task.wait(gameState.waitTime) if not validate() then cleanup(); gameState.fishingInProgress = false; return false end for i = 1, 3 do if not validate() then cleanup(); return false end if gameState.sinker and gameState.casted then local bubble = RepStorage.Splash:Clone() bubble.Caster.Value = player.Name bubble.Parent = workspace bubble.Position = gameState.sinker.Position Debris:AddItem(bubble, 1) end task.wait(0.1) end if not validate() then cleanup(); gameState.fishingInProgress = false; return false end AnimationController:Play("PullingAnimation", 0.1) SoundManager:Play("Reeling", 0.4) GUIManager:SlideFishingFrameIn() if gameState.sinker and gameState.sinker.Parent then gameState.savedHookPosition = gameState.sinker.Position end MinigameSystem:Start(gameState.isAutoFishing, getCurrentRodName()) while MinigameSystem:IsActive() do if not validate() then MinigameSystem:Stop(); break end task.wait(0.1) end gameState.fishingInProgress, gameState.casted = false, false gameState.activeFishingTask = nil if gameState.sinker then gameState.sinker:Destroy(); gameState.sinker = nil end if gameState.splash then gameState.splash:Destroy(); gameState.splash = nil end if RodManager.currentRod and RodManager.currentRod:FindFirstChild("Part") then local line = RodManager.currentRod.Part:FindFirstChild("Line") if line then line:Destroy() end end cleanupCastEvent:FireServer() GUIManager:SlideFishingFrameOut(function() updateMobileButtonText() end) return true end -- ===== CASTING ===== function performCast(power) if not RodManager:IsValidRod() or gameState.casted or not character then return end local hrp = character:FindFirstChild("HumanoidRootPart") if not hrp then return end gameState.hasLanded, gameState.fishingInProgress = false, false gameState.canCast = false updateMobileButtonText() if not gameState.isAutoFishing then AnimationController:Play("WaitingAnimation", 0.1) SoundManager:Play("Cast", 0.5) task.wait(0.4) end if not RodManager.currentRod or not RodManager.currentRod:FindFirstChild("Part") then return end local rodPos = RodManager.currentRod.Part.Position + RodManager.currentRod.Part.CFrame.LookVector * 1.5 local velocity = CastingSystem:CalculateVelocity(rodPos, hrp.CFrame.LookVector, power, 100) castReplicationEvent:FireServer(rodPos, velocity, getCurrentRodName(), power) gameState.sinker = CastingSystem:CreateHook(rodPos, getCurrentRodName()) gameState.sinker.AssemblyLinearVelocity = velocity local att1 = Instance.new("Attachment", RodManager.currentRod.Part) local att2 = Instance.new("Attachment", gameState.sinker) CastingSystem:CreateBeam(RodManager.currentRod.Part, att1, att2, getCurrentRodName()) gameState.casted = true local landingConn; landingConn = gameState.sinker.Touched:Connect(function(hit) if gameState.hasLanded or not gameState.casted or gameState.fishingInProgress then return end if hit:IsDescendantOf(RodManager.currentRod) or hit:IsDescendantOf(character) then return end if CS:HasTag(hit, "Fish") or (hit.Parent and CS:HasTag(hit.Parent, "Fish")) then return end gameState.hasLanded = true gameState.sinker.AssemblyLinearVelocity = Vector3.zero gameState.sinker.Anchored = true if landingConn then landingConn:Disconnect() end local isWater = hit:IsA("Terrain") and CastingSystem:IsPositionInWater(gameState.sinker.Position) if isWater then SoundManager:Play("HookHit", 0.4) local waterY = CastingSystem:GetWaterSurfaceY(gameState.sinker.Position.X, gameState.sinker.Position.Z) if waterY then gameState.sinker.Position = Vector3.new(gameState.sinker.Position.X, waterY + 0.15, gameState.sinker.Position.Z) end local AssetsFolder = RepStorage:FindFirstChild("Assets") local SoundFolder = AssetsFolder and AssetsFolder:FindFirstChild("Sound") local rodSoundTemplate = SoundFolder and SoundFolder:FindFirstChild("Soundeffectrod") -- Digunakan untuk Tahap 2 local EffectFolder = AssetsFolder and AssetsFolder:FindFirstChild("Effect") local RodEffectTemplate = EffectFolder and EffectFolder:FindFirstChild("EffectCorrupRod") -- Digunakan untuk Tahap 2 -- 🔥 LOGIKA BARU: MUNCULKAN EFEK CORRUP HANYA UNTUK ROD TERTENTU -- ASUMSI: DEKLARASI TEMPLATE DAN VARIABEL GLOBAL (HARUS DI ATAS) -- local RodEffectTemplate = nil -- Ganti dengan template PartSecret1 -- local ListrikSoundTemplate = nil -- Ganti dengan template Sound Listrik -- File: FishingSystem (Bagian di mana logika utama berjalan) local VISUAL_EFFECT_DURATION = 2.0 -- Konstanta baru untuk durasi efek visual local totalLockDuration = 4.0 -- local character = player.Character -- Inisialisasi hrp1 (Harus ada di scope yang sama dengan pemanggilan) local hrp1 = character:FindFirstChild("HumanoidRootPart") -- Pastikan Anda menempatkan fungsi helper ini di bagian global script Anda local function positionEffect1(effectClone, template, duration) local hrp = character:FindFirstChild("HumanoidRootPart") if not hrp then return end local distanceInFront = 10 local distanceRight = 0 -- ✅ PERBAIKAN: Menggunakan ketinggian yang lebih aman local heightAboveWater = 1.8 local hrpCFrame = hrp.CFrame local projectedPosition = (hrpCFrame * CFrame.new(distanceRight, 0, -distanceInFront)).Position local finalY = projectedPosition.Y local waterY = CastingSystem:GetWaterSurfaceY(projectedPosition.X, projectedPosition.Z) if waterY then finalY = waterY + heightAboveWater end local finalPosition = Vector3.new(projectedPosition.X, finalY, projectedPosition.Z) -- 1. Hitung CFrame Posisi Tanpa Rotasi local baseCFrame = CFrame.new(projectedPosition.X, finalY, projectedPosition.Z) * (hrpCFrame - hrpCFrame.Position) -- 2. Terapkan Rotasi (Sumbu X) local desiredCFrame = baseCFrame * CFrame.Angles(math.rad(-90), 0, 0) if effectClone:IsA("Model") and effectClone.PrimaryPart then effectClone:SetPrimaryPartCFrame(desiredCFrame) elseif effectClone:IsA("BasePart") then effectClone.CFrame = desiredCFrame end if effectClone:IsA("Model") then for _, child in effectClone:GetChildren() do if child:IsA("BasePart") then child.CanCollide = false child.Anchored = true end end end Debris:AddItem(effectClone, duration) end -- ==================================================================== -- BLOK LOGIKA UTAMA (Dimodifikasi) -- ==================================================================== if getCurrentRodName() == "Corruption Edge" then -- 1. EFEK SUARA if rodSoundTemplate and rodSoundTemplate:IsA("Sound") then local Api = rodSoundTemplate:Clone() Api.Parent = workspace Api:Play() Debris:AddItem(Api, totalLockDuration) -- Dihilangkan setelah 4.0 detik (Sesuai Lock Sequence) else warn("⚠ Efek Suara Listrik tidak ditemukan!") end -- 2. EFEK VISUAL (PARTSECRET1) if RodEffectTemplate and (RodEffectTemplate:IsA("BasePart") or RodEffectTemplate:IsA("Model")) then if hrp1 then local effectClone1 = RodEffectTemplate:Clone() effectClone1.Parent = workspace -- ✅ PERBAIKAN: Menggunakan durasi 3.0 detik positionEffect1(effectClone1, RodEffectTemplate, VISUAL_EFFECT_DURATION) end else warn("⚠️ Template efek visual (PartSecret1) tidak valid atau tidak ditemukan.") end end -- 🔥 AKHIR LOGIKA EFEK CORRUP gameState.activeFishingTask = tick() gameState.fishingInProgress, gameState.canCast = true, false updateMobileButtonText() local splash = RepStorage.Splash:Clone() splash.Caster.Value = player.Name splash.Parent = workspace splash.Position = gameState.sinker.Position gameState.splash = splash task.spawn(runFishingSequence, gameState.activeFishingTask, splash) else GUIManager:ShowNotification("Masukkan ke dalam air!", 3, Color3.fromRGB(255, 200, 100)) task.wait(2) if not RodManager.isEquipped or not RodManager.currentRod then return end gameState.casted = false if gameState.sinker then gameState.sinker:Destroy() end if RodManager.currentRod and RodManager.currentRod:FindFirstChild("Part") then local line = RodManager.currentRod.Part:FindFirstChild("Line") if line then line:Destroy() end end cleanupCastEvent:FireServer() startCastingCooldown() task.delay(0.6, function() if RodManager.isEquipped and RodManager.currentRod then AnimationController:Play("EquippedAnimation", 0.3) end end) end end) end -- ===== ROD EQUIPPED/UNEQUIPPED ===== function onRodEquipped(rod) AnimationController:Reload(humanoid) AnimationController:Play("EquippedAnimation", 0.3) startSpeedMonitoring() GUIManager:ShowAutoButton(true) if isMobile then GUIManager:ShowMobileButton(true) updateMobileButtonText() if connections.mobileButton then connections.mobileButton:Disconnect() end connections.mobileButton = GUIManager:GetElement("tapMobileButton").MouseButton1Click:Connect(function() if not RodManager.isEquipped then return end if gameState.isAutoFishing then return end if MinigameSystem:IsActive() then task.spawn(function() MinigameSystem:HandleClick(SoundManager, GUIManager) end) elseif PowerBarSystem:IsCharging() then local power = PowerBarSystem:StopCharging() if power > 10 and not gameState.castingCooldown then performCast(power) else if not gameState.castingCooldown then GUIManager:ShowNotification("Tenaga tidak cukup!", 2, Color3.fromRGB(255, 200, 100)) end gameState.canCast = true AnimationController:Play("EquippedAnimation", 0.3) end updateMobileButtonText() elseif gameState.canCast and not gameState.casted and not gameState.fishingInProgress and not gameState.castingCooldown then PowerBarSystem:StartCharging(getCurrentRodName()) updateMobileButtonText() end end) else if connections.inputBegan then connections.inputBegan:Disconnect() end connections.inputBegan = UIS.InputBegan:Connect(function(input, processed) if processed or input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end if gameState.isAutoFishing then return end if gameState.canCast and not gameState.casted and not gameState.fishingInProgress and not gameState.castingCooldown then PowerBarSystem:StartCharging(getCurrentRodName()) updateMobileButtonText() end end) if connections.inputEnded then connections.inputEnded:Disconnect() end connections.inputEnded = UIS.InputEnded:Connect(function(input) if (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) and MinigameSystem:IsActive() then task.spawn(function() MinigameSystem:HandleClick(SoundManager, GUIManager) end) elseif input.UserInputType == Enum.UserInputType.MouseButton1 and PowerBarSystem:IsCharging() then if gameState.isAutoFishing then return end local power = PowerBarSystem:StopCharging() if power > 10 and not gameState.castingCooldown then performCast(power) else if not gameState.castingCooldown then GUIManager:ShowNotification("Tenaga tidak cukup!", 2, Color3.fromRGB(255, 200, 100)) end gameState.canCast = true AnimationController:Play("EquippedAnimation", 0.3) end updateMobileButtonText() end end) end end function onRodUnequipped() if PowerBarSystem and PowerBarSystem:IsCharging() then PowerBarSystem:StopCharging() end if MinigameSystem and MinigameSystem:IsActive() then MinigameSystem:Stop() GUIManager:SlideFishingFrameOut(nil) end if gameState.isAutoFishing then gameState.isAutoFishing = false GUIManager:UpdateAutoButton(false) stopAutoFishLoop() end GUIManager:ShowAutoButton(false) AnimationController:Stop() stopSpeedMonitoring() PowerBarSystem:Reset() GUIManager:ShowMobileButton(false) for k, conn in connections do if conn then conn:Disconnect(); connections[k] = nil end end -- Pastikan kamera kembali normal jika unequip if camera.CameraType ~= Enum.CameraType.Custom then camera.CameraType = Enum.CameraType.Custom end if gameState.sinker then gameState.sinker:Destroy(); gameState.sinker = nil end if RodManager.currentRod and RodManager.currentRod:FindFirstChild("Part") then local line = RodManager.currentRod.Part:FindFirstChild("Line") if line then line:Destroy() end end if gameState.casted or gameState.fishingInProgress then cleanupCastEvent:FireServer() end gameState.casted, gameState.fishingInProgress = false, false gameState.canCast, gameState.hasLanded = true, false gameState.fishingCaught, gameState.castingCooldown = false, false end -- ===== CHARACTER EVENTS ===== player.CharacterAdded:Connect(function(char) gameState.activeFishingTask = nil if MinigameSystem:IsActive() then MinigameSystem:ForceStop() end if gameState.sinker then gameState.sinker:Destroy(); gameState.sinker = nil end if gameState.splash then gameState.splash:Destroy(); gameState.splash = nil end cleanupCastEvent:FireServer() -- Pastikan kamera kembali normal jika karakter baru spawn if camera.CameraType ~= Enum.CameraType.Custom then camera.CameraType = Enum.CameraType.Custom end gameState.casted, gameState.fishingInProgress = false, false gameState.canCast, gameState.hasLanded = true, false gameState.fishingCaught, gameState.castingCooldown = false, false gameState.isAutoFishing = false character, humanoid = char, char:WaitForChild("Humanoid") camera = workspace.CurrentCamera -- Update referensi kamera speedState.originalWalkSpeed = humanoid.WalkSpeed initializeSystems() GUIManager:UpdateAutoButton(false) task.wait(1.5) RodManager:SetupCharacterMonitoring(character) end) player.CharacterRemoving:Connect(function() gameState.activeFishingTask = nil if MinigameSystem:IsActive() then MinigameSystem:ForceStop() end if gameState.sinker then gameState.sinker:Destroy(); gameState.sinker = nil end if gameState.splash then gameState.splash:Destroy(); gameState.splash = nil end for k, conn in connections do if conn then conn:Disconnect(); connections[k] = nil end end cleanupCastEvent:FireServer() end) -- ===== CAST REPLICATION ===== castReplicationEvent.OnClientEvent:Connect(function(otherPlayer, rodPos, velocity, rodName) if otherPlayer == player then return end local data = otherPlayersHooks[otherPlayer] if data then if data.sinker then data.sinker:Destroy() end if data.beam then data.beam:Destroy() end if data.connection then data.connection:Disconnect() end end local sinker = CastingSystem:CreateHook(rodPos, rodName) sinker.AssemblyLinearVelocity = velocity local char = otherPlayer.Character if not char then Debris:AddItem(sinker, 5); return end local rod = char:FindFirstChild(rodName) if not rod or not rod:FindFirstChild("Part") then Debris:AddItem(sinker, 5); return end local att1 = Instance.new("Attachment", rod.Part) local att2 = Instance.new("Attachment", sinker) local beam = CastingSystem:CreateBeam(rod.Part, att1, att2, rodName) local conn; conn = sinker.Touched:Connect(function(hit) if hit:IsDescendantOf(char) or CS:HasTag(hit, "Fish") or (hit.Parent and CS:HasTag(hit.Parent, "Fish")) then return end sinker.AssemblyLinearVelocity = Vector3.zero sinker.Anchored = true if conn then conn:Disconnect() end end) otherPlayersHooks[otherPlayer] = {sinker = sinker, beam = beam, connection = conn} end) cleanupCastEvent.OnClientEvent:Connect(function(otherPlayer) if otherPlayer == player then return end local data = otherPlayersHooks[otherPlayer] if data then if data.sinker then data.sinker:Destroy() end if data.beam then data.beam:Destroy() end if data.connection then data.connection:Disconnect() end otherPlayersHooks[otherPlayer] = nil end end) showNotificationEvent.OnClientEvent:Connect(function(message, duration, textColor) GUIManager:ShowNotification(message, duration or 3, textColor or Color3.fromRGB(255, 255, 255)) end) -- ===== INITIAL SETUP ===== task.wait(2) RodManager:SetupCharacterMonitoring(character)