-- ========================================================== -- SweetC Live - Auto Camera Focus Client Script (.luau) -- Lokasi: StarterPlayer -> StarterPlayerScripts -> LocalScript -- Mendengarkan RemoteEvent dari server Roblox: SweetC_CameraFocusEvent -- Menyorot avatar NPC yang baru spawn secara otomatis & transisi halus -- ========================================================== local ReplicatedStorage = game:GetService("ReplicatedStorage") local Players = game:GetService("Players") local RunService = game:GetService("RunService") local TweenService = game:GetService("TweenService") local Workspace = game:GetService("Workspace") local localPlayer = Players.LocalPlayer local camera = Workspace.CurrentCamera print("=========================================================") print("🎥 [SweetC Camera Focus] Client Script Aktif & Siap.") print("=========================================================") -- State local isFocusing = false local currentTarget = nil local focusEndTime = 0 local focusConnection = nil local focusQueue = {} local currentOverlapBehavior = "QUEUE" -- Helper untuk merestorasi kamera ke kondisi normal karakter pemain lokal local function restoreCameraToLocalPlayer() if focusConnection then focusConnection:Disconnect() focusConnection = nil end isFocusing = false currentTarget = nil focusEndTime = 0 local character = localPlayer.Character local humanoid = character and character:FindFirstChildOfClass("Humanoid") camera.CameraType = Enum.CameraType.Custom if humanoid then camera.CameraSubject = humanoid end print("[SweetC Camera Focus] 🔄 Kamera dikembalikan normal ke karakter pemain.") end -- Ambil part target yang valid (HumanoidRootPart atau Head) local function getTargetPart(model) if not model or not model.Parent then return nil end return model:FindFirstChild("HumanoidRootPart") or model:FindFirstChild("Head") or model.PrimaryPart end -- Proses antrean fokus berikutnya jika ada local function processNextInQueue() if #focusQueue > 0 then local nextItem = table.remove(focusQueue, 1) task.defer(function() startCameraFocus(nextItem) end) else restoreCameraToLocalPlayer() end end -- Mulai fokus kamera ke model target function startCameraFocus(payload) if not payload then return end local targetName = payload.targetModelName local duration = tonumber(payload.duration) or 5 local transition = payload.transition or "SMOOTH" local distance = tonumber(payload.distance) or 10 local elevationAngle = tonumber(payload.cameraAngle) or 15 local overlapBehavior = payload.overlapBehavior or "QUEUE" -- Cari model target di Workspace atau SweetC_Participants local participantsFolder = Workspace:FindFirstChild("SweetC_Participants") local targetModel = nil if participantsFolder then targetModel = participantsFolder:FindFirstChild(targetName) end if not targetModel then targetModel = Workspace:FindFirstChild(targetName) end if not targetModel then warn("[SweetC Camera Focus] ⚠️ Model target tidak ditemukan: " .. tostring(targetName)) processNextInQueue() return end local targetPart = getTargetPart(targetModel) if not targetPart then warn("[SweetC Camera Focus] ⚠️ TargetPart tidak ditemukan pada model: " .. tostring(targetName)) processNextInQueue() return end -- Hentikan fokus sebelumnya jika ada if focusConnection then focusConnection:Disconnect() focusConnection = nil end isFocusing = true currentTarget = targetModel focusEndTime = os.clock() + duration currentOverlapBehavior = overlapBehavior camera.CameraType = Enum.CameraType.Scriptable local angleRad = math.rad(elevationAngle) local heightOffset = math.sin(angleRad) * distance + 2.5 local backOffset = math.cos(angleRad) * distance print(string.format("[SweetC Camera Focus] 🎯 Menyorot avatar @%s selama %d detik (Mode: %s, Jarak: %d studs)", targetName:gsub("^NPC_", ""), duration, transition, distance)) -- Jika transisi SNAP, langsung pasang posisi awal if transition == "SNAP" then local targetPos = targetPart.Position + Vector3.new(0, 1.5, 0) local camPos = targetPos + (targetPart.CFrame.LookVector * -backOffset) + Vector3.new(0, heightOffset, 0) camera.CFrame = CFrame.lookAt(camPos, targetPos) end -- Sambungkan pergerakan kamera ke RenderStepped focusConnection = RunService.RenderStepped:Connect(function(dt) -- Validasi keselamatan: jika target hancur, dihapus, atau durasi habis if not isFocusing or not currentTarget or not currentTarget.Parent or not targetPart or not targetPart.Parent then restoreCameraToLocalPlayer() processNextInQueue() return end if os.clock() >= focusEndTime then restoreCameraToLocalPlayer() processNextInQueue() return end local targetPos = targetPart.Position + Vector3.new(0, 1.2, 0) local lookDir = targetPart.CFrame.LookVector local camPosTarget = targetPos - (lookDir * backOffset) + Vector3.new(0, heightOffset, 0) local targetCFrame = CFrame.lookAt(camPosTarget, targetPos) if transition == "SMOOTH" then -- Lerp halus dengan faktor interpolasi adaptif local lerpFactor = math.clamp(dt * 6.0, 0.05, 0.4) camera.CFrame = camera.CFrame:Lerp(targetCFrame, lerpFactor) else camera.CFrame = targetCFrame end end) end -- Dengarkan RemoteEvent dari Server local function setupRemoteListener() local remoteEvent = ReplicatedStorage:WaitForChild("SweetC_CameraFocusEvent", 10) if not remoteEvent then warn("[SweetC Camera Focus] Menunggu ReplicatedStorage.SweetC_CameraFocusEvent...") remoteEvent = ReplicatedStorage:WaitForChild("SweetC_CameraFocusEvent") end remoteEvent.OnClientEvent:Connect(function(payload) if not payload or type(payload) ~= "table" then return end local overlap = payload.overlapBehavior or "QUEUE" if isFocusing then if overlap == "IMMEDIATE_SWITCH" then -- Langsung ganti ke target terbaru print("[SweetC Camera Focus] ⚡ Ganti fokus seketika ke avatar baru!") startCameraFocus(payload) elseif overlap == "IGNORE_NEW" then -- Abaikan yang baru sampai durasi selesai print("[SweetC Camera Focus] ⏭️ Mengabaikan fokus baru (sedang aktif fokus lain).") else -- Default: Antrekan (QUEUE) print("[SweetC Camera Focus] 📥 Mengantrekan fokus kamera untuk avatar: " .. tostring(payload.targetModelName)) table.insert(focusQueue, payload) end else startCameraFocus(payload) end end) end -- Dengarkan jika karakter lokal respawn (kamera harus kembali aman) localPlayer.CharacterAdded:Connect(function(newChar) focusQueue = {} restoreCameraToLocalPlayer() end) -- Mulai listener task.spawn(setupRemoteListener)