0% found this document useful (0 votes)
7 views59 pages

Platform Manager

The document is a Lua script for managing a game platform in Roblox, including player interactions, score management, and anti-cheat mechanisms. It sets up various game states, handles player platforms, and manages events for game actions like answering questions and scoring. The script also includes functions for monitoring player positions to prevent cheating and ensuring fair gameplay.

Uploaded by

dungpoop21092006
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views59 pages

Platform Manager

The document is a Lua script for managing a game platform in Roblox, including player interactions, score management, and anti-cheat mechanisms. It sets up various game states, handles player platforms, and manages events for game actions like answering questions and scoring. The script also includes functions for monitoring player positions to prevent cheating and ensuring fair gameplay.

Uploaded by

dungpoop21092006
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

-- PlatformManager.

lua

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local TweenService = game:GetService("TweenService")

local Players = game:GetService("Players")

local CollectionService = game:GetService("CollectionService")

local gameStarted = false

local recommendedSongsQueue = {}

local roundCount = 0

local nextRecommendationRound = 2

local incorrectPlayers = {}

local hasRecommendedThisMatch = {}

local playerScores = {}

local mapInitialized = false -- đặt ở đầu script PlatformManager nếu chưa có

-- Workspace Objects

local lavaPart = workspace:WaitForChild("Lava")

local initialLavaY = [Link].Y

local lobbySpawn = workspace:WaitForChild("LobbySpawn")

local safePlatformTemplate = workspace:WaitForChild("SafePlatformTemplate")

[Link] = 1

local safePlatformsFolder = workspace:WaitForChild("SafePlatforms")

[Link] = "SafePlatforms"

-- State

local bestSnapshotEver = {}

local top3Snapshot = {}

local finalLeaderboard = {}
local hoverCounter = {}

local playerPlatformMap = {} -- giữ lại map bạn đang dùng

local platformOwnerMap = {} --map phụ để quản lý Owner platform

local burning = {}

local answerTimes = {}

local playerScores = {}

local justRaised = {}

local totalAnswers = 0

local roundStartTime = 0

local floatCounter = {} -- Đếm số lần người chơi vi phạm

local graceTime = {}

-- RemoteEvents

local smoothevent = ReplicatedStorage:WaitForChild("SmoothTweenPlayer")

local sendQuestionEvent = ReplicatedStorage:WaitForChild("SendQuestion")

local answerResultEvent = ReplicatedStorage:WaitForChild("AnswerResult")

local lobbyTimerEvent = ReplicatedStorage:WaitForChild("LobbyTimer")

local tweenPlayerEvent = ReplicatedStorage:FindFirstChild("TweenPlayerUp")

local displayLeaderboardEvt = ReplicatedStorage:FindFirstChild("DisplayLeaderboard")

if not displayLeaderboardEvt then

displayLeaderboardEvt = [Link]("RemoteEvent", ReplicatedStorage)

[Link] = "DisplayLeaderboard"

end

local displayScoreboardEvt = ReplicatedStorage:FindFirstChild("DisplayScoreboard")

if not displayScoreboardEvt then


displayScoreboardEvt = [Link]("RemoteEvent", ReplicatedStorage)

[Link] = "DisplayScoreboard"

end

local showLeaderboardUIEvt = ReplicatedStorage:FindFirstChild("ShowLeaderboardUI")

if not showLeaderboardUIEvt then

showLeaderboardUIEvt = [Link]("RemoteEvent", ReplicatedStorage)

[Link] = "ShowLeaderboardUI"

end

local recommendSongsEvent = ReplicatedStorage:FindFirstChild("RecommendSongs")

local ScoreRewardEvent = ReplicatedStorage:FindFirstChild("ScoreRewardEvent")

if not ScoreRewardEvent then

ScoreRewardEvent = [Link]("RemoteEvent")

[Link] = "ScoreRewardEvent"

[Link] = ReplicatedStorage

end

if not recommendSongsEvent then

recommendSongsEvent = [Link]("RemoteEvent")

[Link] = "RecommendSongs"

[Link] = ReplicatedStorage

end

local recommendationNotificationEvent =
ReplicatedStorage:FindFirstChild("RecommendationNotification")

if not recommendationNotificationEvent then

recommendationNotificationEvent = [Link]("RemoteEvent")

[Link] = "RecommendationNotification"
[Link] = ReplicatedStorage

end

local stopMusicEvent = ReplicatedStorage:FindFirstChild("StopMusic")

if not stopMusicEvent then

stopMusicEvent = [Link]("RemoteEvent", ReplicatedStorage)

[Link] = "StopMusic"

end

local gameStateEvent = ReplicatedStorage:FindFirstChild("GameState")

if not gameStateEvent then

gameStateEvent = [Link]("RemoteEvent", ReplicatedStorage)

[Link] = "GameState"

end

local SubtractScore = ReplicatedStorage:WaitForChild("SubtractScore")

local AFKStatusChanged = ReplicatedStorage:WaitForChild("AFKStatusChanged")

[Link]:Connect(function(player, cost)

if typeof(cost) ~= "number" or cost < 0 then return end

local stats = player:FindFirstChild("leaderstats")

if not stats then return end

local score = stats:FindFirstChild("Score")

if not score then return end

if [Link] >= cost then

[Link] -= cost

end

end)

local function cloneAppearanceToDummy(player, dummy)


if not player or not dummy then return end

-- ensure dummy has a Humanoid

local humanoid = dummy:FindFirstChildOfClass("Humanoid")

if not humanoid then

humanoid = [Link]("Humanoid")

[Link] = "Humanoid"

[Link] = dummy

end

-- 1) Lưu lại các scale gốc

local originalScales = {}

for _, name in
ipairs({"BodyDepthScale","BodyHeightScale","BodyWidthScale","HeadScale"}) do

local val = humanoid:FindFirstChild(name)

if val then originalScales[name] = [Link] end

end

-- 2) Lấy và apply HumanoidDescription

local success, description = pcall(function()

return Players:GetHumanoidDescriptionFromUserId([Link])

end)

if success and description then

humanoid:ApplyDescription(description)

-- 3) Khôi phục scale gốc

for name, v in pairs(originalScales) do


local obj = humanoid:FindFirstChild(name)

if obj then [Link] = v end

end

else

warn("[Podium] Không thể lấy description của ".. [Link])

end

end

local function checkLavaKill()

local lavaBottomY = [Link].Y - [Link].Y / 2

for _, pl in ipairs(Players:GetPlayers()) do

-- Chỉ kill khi InGame và còn platform

if not pl:GetAttribute("InGame") then

continue

end

if not playerPlatformMap[[Link]] then

continue

end

if pl:GetAttribute("ImmuneLava") then

continue

end

local char = [Link]

local head = char and char:FindFirstChild("Head")

local hum = char and char:FindFirstChildOfClass("Humanoid")

local root = char and char:FindFirstChild("HumanoidRootPart")


-- 🔹 Bỏ qua nếu đang đứng trên SafePlatform của mình

local safePlat = workspace:FindFirstChild("SafePlatforms") and


[Link]:FindFirstChild([Link] .. "_SafePlatform")

if safePlat and root and ([Link] - [Link]).Magnitude < 6 then

continue

end

if head and hum and [Link] > 0 then

local headY = [Link].Y

if lavaBottomY >= headY then

[Link] = 0

warn(("[LavaKill] %s bị giết vì lava vượt qua


đầu"):format([Link]))

end

end

end

end

local function getDistanceToGround(pos)

local rayOrigin = pos

local rayDirection = [Link](0, -1000, 0) -- bắn xuống

local rayParams = [Link]()

[Link] = {workspace:FindFirstChild("SafePlatforms")}

[Link] = [Link]
local result = workspace:Raycast(rayOrigin, rayDirection, rayParams)

if result then

return (pos - [Link]).Magnitude

else

return [Link]

end

end

local MAX_DISTANCE = 15

local MAX_HOVER_HEIGHT = 30

local DETECTION_LIMIT = 2

local function checkAntiCheatPosition()

local safeFolder = workspace:FindFirstChild("SafePlatforms")

if not safeFolder then

warn("Không tìm thấy folder SafePlatforms trong workspace")

return

end

for _, player in ipairs(Players:GetPlayers()) do

local char = [Link]

if not char or not char:IsDescendantOf(workspace) then continue end

local humanoid = char:FindFirstChildOfClass("Humanoid")

local root = char:FindFirstChild("HumanoidRootPart")

if not humanoid or [Link] <= 0 or not root then continue end


if player:GetAttribute("IsAFK") then continue end

if not player:GetAttribute("InGame") then continue end

-- Nếu còn trong grace → bỏ qua và reset counter

if graceTime[[Link]] and tick() < graceTime[[Link]] then

floatCounter[[Link]] = 0

hoverCounter[[Link]] = 0

continue

end

local platform = safeFolder:FindFirstChild([Link] .. "_SafePlatform")

if not platform then

warn("[AntiCheat] Không tìm thấy SafePlatform cho " .. [Link])

continue

end

-- 1) Bay quá xa khỏi SafePlatform

local dist = ([Link] - [Link]).Magnitude

if dist > MAX_DISTANCE then

floatCounter[[Link]] = (floatCounter[[Link]] or 0) + 1

if floatCounter[[Link]] >= DETECTION_LIMIT then

warn(("[AntiCheat] %s bay xa platform: %.1f


studs"):format([Link], dist))

player:Kick("AntiCheat: Phát hiện bay ra khỏi platform.")

end

else
floatCounter[[Link]] = 0

end

-- 2) Bay cao lơ lửng (không đứng trên gì cả)

local height = getDistanceToGround([Link])

if height > MAX_HOVER_HEIGHT then

hoverCounter[[Link]] = (hoverCounter[[Link]] or 0) + 1

if hoverCounter[[Link]] >= DETECTION_LIMIT then

warn(("[AntiCheat] %s lơ lửng: %.1f studs"):format([Link],


height))

player:Kick("AntiCheat: Phát hiện lơ lửng bất thường.")

end

else

hoverCounter[[Link]] = 0

end

end

end

-- ====== CẤU HÌNH NGUỒN HỢP LỆ VÀ GIỚI HẠN ======

local SCORE_LIMITS = {

["AnswerCorrect"] = 5, -- Trả lời đúng: tối đa 5 điểm / lần

["BonusRound"] = 1000,-- Bonus round (v10, v20, v30): tối đa 1000 điểm / lần

["Donation"] = 999999 -- Donate: gần như không giới hạn

-- === A) Helper: đảm bảo tạo/nhận platform cho 1 player ===

local function ensurePlatformForPlayer(pl)

if not pl or not [Link] or pl:GetAttribute("IsAFK") then


return nil

end

-- Nếu đã có sẵn và còn parent thì dùng luôn

local existingPlat = playerPlatformMap[[Link]]

if existingPlat and [Link] then

return existingPlat

end

-- Đảm bảo folder tồn tại

if not safePlatformsFolder or not [Link] then

safePlatformsFolder = workspace:FindFirstChild("SafePlatforms")

if not safePlatformsFolder then

safePlatformsFolder = [Link]("Folder")

[Link] = "SafePlatforms"

[Link] = workspace

print("[ensurePlatformForPlayer] Tạo mới folder SafePlatforms")

end

end

-- Tính vị trí theo grid hiện có

local current = #safePlatformsFolder:GetChildren()

local spacing = [Link].X + 2

local perRow = [Link]([Link](current + 1))

local row = current // perRow

local col = current % perRow


local offsetX = (col - (perRow-1)/2) * spacing

local offsetZ = (row - (perRow-1)/2) * spacing

local pos = [Link](

[Link].X + offsetX,

[Link].Y,

[Link].Z + offsetZ

-- Tạo platform

local clone = safePlatformTemplate:Clone()

[Link] = tostring([Link]).."_SafePlatform"

[Link] = 0

[Link] = true

[Link] = true

[Link] = safePlatformsFolder

[Link] = [Link](pos)

playerPlatformMap[[Link]] = clone

print(("[ensurePlatformForPlayer] ✅ Tạo platform cho %s tại (%.1f, %.1f, %.1f)")

:format([Link], pos.X, pos.Y, pos.Z))

return clone

end

-- ====== HÀM CỘNG ĐIỂM AN TOÀN ======


local function addScore(player, amount, source)

-- 1. Kiểm tra player hợp lệ

if not player or not player:IsA("Player") then

return false

end

-- 2. Kiểm tra nguồn cộng điểm hợp lệ

local maxAllowed = SCORE_LIMITS[source]

if not maxAllowed then

warn([Link]("[ANTI-CHEAT] %s (%d) cố gắng cộng điểm từ nguồn không


hợp lệ: %s",

[Link], [Link], tostring(source)))

player:Kick("Cộng điểm từ nguồn không hợp lệ!")

return false

end

-- 3. Giới hạn số điểm / lần theo từng nguồn

if type(amount) ~= "number" or amount <= 0 or amount > maxAllowed then

warn([Link](

"[ANTI-CHEAT] %s (%d) cố gắng cộng %s điểm từ nguồn %s (quá giới hạn


%d)",

[Link], [Link], tostring(amount), source, maxAllowed

))

player:Kick("Cộng điểm quá giới hạn!")

return false

end

-- 4. Cộng điểm
local ls = player:FindFirstChild("leaderstats")

local sv = ls and ls:FindFirstChild("Score")

if sv then

[Link] = [Link] + amount

print([Link]("[addScore] %s +%d điểm (%s) → %d",

[Link], amount, source, [Link]))

return true

end

return false

end

local function isPlayerAliveAndOnPlatform(plr)

local char = [Link]

local humanoid = char and char:FindFirstChildOfClass("Humanoid")

local root = char and char:FindFirstChild("HumanoidRootPart")

if not humanoid or [Link] <= 0 then return false end

if not root or not plr:GetAttribute("InGame") then return false end

-- Kiểm tra còn SafePlatform không

local safeFolder = workspace:FindFirstChild("SafePlatforms")

local platform = safeFolder and safeFolder:FindFirstChild([Link] .. "_SafePlatform")

if not platform then return false end

-- Kiểm tra player còn trong playerPlatformMap

if not playerPlatformMap[[Link]] then return false end


return true

end

-- 🧹 Xóa hết Owner của các platform

local function clearAllPlatformOwners()

if not safePlatformsFolder then return end

for _, plat in ipairs(safePlatformsFolder:GetChildren()) do

plat:SetAttribute("Owner", nil)

end

print("[Platform] ✅ Đã clear toàn bộ Owner trong SafePlatforms")

end

-- 🎯 Gán platform cho player (nếu trống)

local function assignPlatformForPlayer(plr)

if not plr or not [Link] then return nil end

if not safePlatformsFolder then return nil end

-- Nếu player đã có platform còn tồn tại thì dùng luôn

local existing = playerPlatformMap[[Link]]

if existing and [Link] then

return existing

end

-- Tìm platform trống

for _, plat in ipairs(safePlatformsFolder:GetChildren()) do

if not plat:GetAttribute("Owner") then


plat:SetAttribute("Owner", [Link])

playerPlatformMap[[Link]] = plat

print([Link]("[Platform] Gán %s vào %s", [Link], [Link]))

return plat

end

end

warn("[Platform] Không tìm thấy platform trống cho " .. [Link])

return nil

end

-- 🔄 Trả platform khi player rời game

local function releasePlatformForPlayer(plr)

if not plr then return end

if not safePlatformsFolder then return end

local plat = playerPlatformMap[[Link]]

if plat and [Link] then

plat:SetAttribute("Owner", nil)

print([Link]("[Platform] %s rời game → trả lại %s", [Link], [Link]))

end

playerPlatformMap[[Link]] = nil

end

local function teleportCharacterToPosition(character, position)

if not character or not [Link] then


return

end

local root = character:FindFirstChild("HumanoidRootPart") or


character:WaitForChild("HumanoidRootPart", 5)

local humanoid = character:FindFirstChildOfClass("Humanoid")

local player = Players:GetPlayerFromCharacter(character)

if not (root and humanoid) then

warn("[Teleport] Không tìm thấy HRP hoặc Humanoid cho nhân vật")

return

end

-- Bật miễn lava tạm thời

if player and [Link] then

player:SetAttribute("ImmuneLava", true)

end

-- 1. Reset velocity để ngăn rơi/đẩy lệch

[Link] = [Link]

[Link] = [Link]

-- 2. Khóa vật lý tạm thời

[Link] = true

[Link] = true
-- 3. Teleport cao hơn platform 5 studs

[Link] = [Link](position + [Link](0, 5, 0))

-- 4. Đợi 1 frame vật lý

[Link](0.15)

-- 5. Mở lại vật lý

[Link] = false

[Link] = false

-- 6. Ép nhân vật đứng vững

if humanoid:GetState() ~= [Link] then

humanoid:ChangeState([Link])

end

-- 7. Tắt miễn lava sau khi teleport

if player and [Link] then

[Link](0.05, function()

if player and [Link] then

player:SetAttribute("ImmuneLava", false)

end

end)

end

print([Link]("[Teleport] ✅ %s đã được dịch chuyển đến (%.1f, %.1f, %.1f)",

player and [Link] or "???", position.X, position.Y, position.Z))


end

local function resetMap()

-- 0) Đặt lại trạng thái InGame cho tất cả người chơi

for _, player in ipairs(Players:GetPlayers()) do

player:SetAttribute("InGame", false)

end

-- 0.1) Đảm bảo folder SafePlatforms tồn tại

if not safePlatformsFolder or not [Link] then

safePlatformsFolder = workspace:FindFirstChild("SafePlatforms")

if not safePlatformsFolder then

safePlatformsFolder = [Link]("Folder")

[Link] = "SafePlatforms"

[Link] = workspace

print("[resetMap] Tạo mới folder SafePlatforms")

end

end

-- 1) Reset vị trí lava ban đầu

[Link] = [Link]([Link].X, initialLavaY, [Link].Z)

-- 2) Xóa toàn bộ platform cũ

for _, child in ipairs(safePlatformsFolder:GetChildren()) do

child:Destroy()

end

playerPlatformMap = {}
-- 3) Reset các cờ bonus

for _, plr in ipairs(Players:GetPlayers()) do

plr:SetAttribute("Got10Bonus", false)

plr:SetAttribute("Got20Bonus", false)

plr:SetAttribute("Got30Bonus", false)

end

-- 4) Tạo lại platform cho mỗi người chơi

local allPlayers = Players:GetPlayers()

local n = #allPlayers

if n == 0 then

print("[resetMap] Không có người chơi để tạo platform")

return

end

local size = [Link].X

local spacing = size + 2

local perRow = [Link]([Link](n))

local originX = [Link].X

local originZ = [Link].Z

local platformY = [Link].Y

local createdCount = 0

for i, pl in ipairs(allPlayers) do

if pl:GetAttribute("IsAFK") then
continue

end

local idx = i - 1

local row = [Link](idx / perRow)

local col = idx % perRow

local offsetX = (col - (perRow-1)/2) * spacing

local offsetZ = (row - (perRow-1)/2) * spacing

local pos = [Link](originX + offsetX, platformY, originZ + offsetZ)

local clone = safePlatformTemplate:Clone()

[Link] = tostring([Link]).."_SafePlatform"

[Link] = 0

[Link] = true

[Link] = true

[Link] = safePlatformsFolder

[Link]() -- 1 frame để Roblox cập nhật physics

[Link] = [Link](pos)

playerPlatformMap[[Link]] = clone

createdCount += 1

-- Teleport về lobby & reset stats

if [Link] then
teleportCharacterToPosition([Link], [Link] +
[Link](0,5,0))

local h = [Link]:FindFirstChildOfClass("Humanoid")

if h then

[Link] = 16

[Link] = 50

[Link] = [Link]

h:SetAttribute("NoHeal", false)

end

end

end

-- 5) Đợi một chút để đảm bảo replicate platform tới client

[Link](0.5)

-- 6) Gửi trạng thái InGame = false

gameStateEvent:FireAllClients(false)

-- 7) Reset biến chơi

roundCount = 0

totalAnswers = 0

roundStartTime = 0

recommendedSongsQueue = {}

nextRecommendationRound = 1

answerTimes = {}

incorrectPlayers = {}
hasRecommendedThisMatch = {}

justRaised = {}

print([Link]("[resetMap] Hoàn tất: %d platform mới tạo", createdCount))

end

local OnPlayerDonated = ReplicatedStorage:WaitForChild("OnPlayerDonated")

local EquipSkinEvent = ReplicatedStorage:WaitForChild("EquipSkinEvent")

local ApplySkinEvent = ReplicatedStorage:WaitForChild("ApplySkinEvent")

-- Sau chỗ: local ApplySkinEvent = ReplicatedStorage:WaitForChild("ApplySkinEvent")

[Link]:Connect(function(player)

-- 1) Nếu là người đầu tiên → reset map

if not gameStarted and not mapInitialized then

resetMap()

mapInitialized = true

end

-- 2) Ghi nhận điểm

local ls = player:FindFirstChild("leaderstats")

playerScores[[Link]] = ls and (ls:FindFirstChild("Score") or { Value = 0 }).Value or 0

-- 3) Nếu game chưa bắt đầu nhưng map đã reset → tạo platform cho người chơi mới

if not gameStarted and mapInitialized and not playerPlatformMap[[Link]] and not


player:GetAttribute("IsAFK") then

-- Đếm số platform hiện tại

local existing = #safePlatformsFolder:GetChildren()


local spacing = [Link].X + 2

local numPerRow = [Link]([Link](existing + 1))

local row = existing // numPerRow

local col = existing % numPerRow

local offsetX = (col - numPerRow // 2) * spacing

local offsetZ = (row - numPerRow // 2) * spacing

local pos = [Link](

[Link].X + offsetX,

[Link].Y,

[Link].Z + offsetZ

local clone = safePlatformTemplate:Clone()

[Link] = [Link] .. "_SafePlatform"

[Link] = 0

[Link] = true

[Link] = pos

[Link] = safePlatformsFolder

playerPlatformMap[[Link]] = clone

end

-- 4) Gửi skin

player:GetAttributeChangedSignal("EquippedSkin"):Connect(function()

local skinId = player:GetAttribute("EquippedSkin") or ""

ApplySkinEvent:FireAllClients([Link], skinId)

end)
[Link](function()

local skinId = player:GetAttribute("EquippedSkin") or ""

ApplySkinEvent:FireAllClients([Link], skinId)

end)

-- 5) Character spawn

[Link]:Connect(function(char)

local humanoid = char:WaitForChild("Humanoid")

local root = char:WaitForChild("HumanoidRootPart")

burning[humanoid] = nil

graceTime[[Link]] = tick() + 3

floatCounter[[Link]] = 0

[Link]:Connect(function()

graceTime[[Link]] = tick() + 3

floatCounter[[Link]] = 0

if player then player:SetAttribute("ImmuneLava", false) end

end)

-- 1) Teleport về lobby, reset stats

teleportCharacterToPosition(char, [Link] + [Link](0, 5, 0))

local humanoid = char:WaitForChild("Humanoid")

-- 2) Cho phép hồi máu (lobby)

humanoid:SetAttribute("NoHeal", false)
-- Bỏ hẳn listener block-health ở đây

-- 3) Khi chết → dọn platform

[Link]:Connect(function()

local p = playerPlatformMap[[Link]]

if p then p:Destroy() end

playerPlatformMap[[Link]] = nil

end)

end)

if [Link] then

teleportCharacterToPosition([Link], [Link] +
[Link](0, 5, 0))

end

end)

[Link]:Connect(function(player, skinId)

-- skinId == "" nghĩa là gỡ skin

if type(skinId) ~= "string" then return end

-- lưu vào Attribute để sau này còn load lại

player:SetAttribute("EquippedSkin", skinId)

print(("[Server] [Link] từ %s, skinId = %q"):format([Link],


skinId))

-- broadcast cho tất cả client biết player này vừa thay đổi skin

ApplySkinEvent:FireAllClients([Link], skinId)
print(("[Server] Fire ApplySkinEvent → userId=%d, skinId=%q"):format([Link],
skinId))

end)

[Link]:Connect(function(player, isAFK)

if typeof(isAFK) ~= "boolean" then return end

player:SetAttribute("IsAFK", isAFK)

-- Nếu đang ở lobby (chưa start) và vừa tắt AFK -> chuẩn bị platform ngay

if not gameStarted and not isAFK then

local plat = ensurePlatformForPlayer(player)

if plat then

print(("[AFK] %s tắt AFK → chuẩn bị sẵn platform"):format([Link]))

end

end

end)

-- Hàm lấy data hiện tại để gửi lên client

local function collectScoreboardData()

local data = {}

for _, pl in ipairs(Players:GetPlayers()) do

local ls = pl:FindFirstChild("leaderstats")

if ls then

local sv = ls:FindFirstChild("Score")

[Link](data, {

player = [Link],

score = sv and [Link] or 0


})

end

end

return data

end

-- Khi có donate thành công

[Link]:Connect(function(pl, robuxAmount)

-- tương tự handler ở [Link]: Score đã được cộng vào leaderstats

-- giờ chỉ cần cập nhật lại bảng điểm ngay lập tức

local data = collectScoreboardData()

displayScoreboardEvt:FireAllClients(data)

end)

local RoundUpdateEvent = ReplicatedStorage:FindFirstChild("RoundUpdate")

[Link]:Connect(function(player, payload)

-- 1) Kiểm tra payload

if typeof(payload) ~= "table" then return end

local songsTable = [Link]

if typeof(songsTable) ~= "table" or #songsTable == 0 then return end

if #songsTable > 3 then

warn(("[PlatformManager] %s gửi quá nhiều bài (%d), giới hạn là


3"):format([Link], #songsTable))

return

end
-- 2) Lấy leaderstats và Score

local leaderstats = player:FindFirstChild("leaderstats")

local scoreVal = leaderstats and leaderstats:FindFirstChild("Score")

if not scoreVal then return end

-- 3) Tính cost thực tế (không tin client gửi)

local function calculateCost(n)

if n == 1 then return 10 end

if n == 2 then return 30 end

if n == 3 then return 45 end

return [Link]

end

local realCost = calculateCost(#songsTable)

if [Link] < realCost then

warn(("[PlatformManager] %s không đủ điểm (%d < %d) để đề xuất!")

:format([Link], [Link], realCost))

return

end

-- 4) Chặn spam (1 lần mỗi round)

if player:GetAttribute("UsedRecommend") then

warn(("[PlatformManager] %s đã dùng Recommend trong round


này!"):format([Link]))

return

end
player:SetAttribute("UsedRecommend", true)

-- 5) Validate & đưa từng questionId vào queue

for _, entry in ipairs(songsTable) do

local qid = [Link]

if typeof(qid) == "string" then

local mod =
[Link]:FindFirstChild("MusicQuestions"):FindFirstChild(qid)

if mod and mod:IsA("ModuleScript") then

[Link](recommendedSongsQueue, qid)

else

warn(("[PlatformManager] %s gửi qid không hợp lệ:


%s"):format([Link], tostring(qid)))

end

end

end

-- 6) Trừ điểm

[Link] -= realCost

-- 7) Tính các vòng mà player này đề xuất

local count = #songsTable

local rounds = {}

local startRound = nextRecommendationRound

for i = 0, count - 1 do

[Link](rounds, startRound + i)

end
nextRecommendationRound = startRound + count

-- 8) Gửi notification cho tất cả client

recommendationNotificationEvent:FireAllClients([Link], count, rounds)

-- 9) Debug log

print([Link](

"[PlatformManager] %s đã đề xuất %d bài (cost=%d) cho vòng %s",

[Link], count, realCost, [Link](rounds, ",")

))

print("[PlatformManager] Queue size =", #recommendedSongsQueue)

end)

-- Configurations

local QUESTIONS_FOLDER = ReplicatedStorage:WaitForChild("MusicQuestions")

local TIME_LIMIT = 10

local LAVA_STEP_PER_ROUND = 3.5

local STEP_INCREMENT =1

local MAX_SAFE_STEP =9

local TWEEN_TIME = 1.5

local LOBBY_MIN_PLAYERS =1

local LOBBY_MAX_WAIT = 30

local START_COUNTDOWN_TIME = 20

local GRID_COLS =3

local GRID_ROWS =3

local GRID_SPACING_X = [Link].X + 2

local GRID_SPACING_Z = [Link].Z + 2


local ORIGIN_POS = [Link]

local DAMAGE_PER_SECOND =4

local tweenInfo = [Link](TWEEN_TIME, [Link],


[Link])

local function tweenPlatformUsingGuide(player, boost)

local plat = playerPlatformMap[[Link]]

if not plat then return end

if player and [Link] then

player:SetAttribute("ImmuneLava", true)

end

-- Tạo Part guide

local guide = [Link]("Part")

[Link] = [Link] .. "_GuidePart"

[Link] = true

[Link] = false

[Link] = 1

[Link] = [Link](1, 1, 1)

[Link] = [Link]

[Link] = workspace

-- Tạo attachments

local att0 = [Link]("Attachment", plat)

local att1 = [Link]("Attachment", guide)


-- AlignPosition

local alignPos = [Link]("AlignPosition")

alignPos.Attachment0 = att0

alignPos.Attachment1 = att1

[Link] = true

[Link] = true

[Link] = 200

[Link] = [Link]

[Link] = plat

-- AlignOrientation

local alignOri = [Link]("AlignOrientation")

alignOri.Attachment0 = att0

alignOri.Attachment1 = att1

[Link] = true

[Link] = 200

[Link] = [Link]

[Link] = plat

-- Cấu hình platform

[Link] = false

[Link] = true

[Link] = false

plat:SetNetworkOwner(nil)
-- Tween guide đi lên

local info = [Link](TWEEN_TIME, [Link],


[Link])

local tween = TweenService:Create(guide, info, {

Position = [Link] + [Link](0, boost, 0)

})

tween:Play()

-- Cleanup guide sau 3s

[Link](TWEEN_TIME + 0.05, function()

if guide and [Link] then guide:Destroy() end

-- Sau khi tween xong → khoá platform lại

if plat and [Link] then

[Link] = true

-- Xoá Align nếu không dùng nữa

local ap = plat:FindFirstChildOfClass("AlignPosition")

if ap then ap:Destroy() end

local ao = plat:FindFirstChildOfClass("AlignOrientation")

if ao then ao:Destroy() end

local atts = plat:GetChildren()

for _, obj in pairs(atts) do

if obj:IsA("Attachment") then
obj:Destroy()

end

end

end

if player and [Link] then

-- chờ thêm 0.05s để đảm bảo physics ổn định

[Link](0.05)

player:SetAttribute("ImmuneLava", false)

end

end)

end

-- LẤY CÂU HỎI NGẪU NHIÊN

local function getRandomQuestion()

local modules = QUESTIONS_FOLDER:GetChildren()

local pickedModule

if #recommendedSongsQueue > 0 then

local nextName = [Link](recommendedSongsQueue, 1)

for _, mod in ipairs(modules) do

if [Link] == nextName then

pickedModule = mod

break

end

end

end
if not pickedModule then

if #modules > 0 then

pickedModule = modules[[Link](1, #modules)]

else

error("[PlatformManager] MusicQuestion folder is empty!")

end

end

local pickedData = require(pickedModule)

-- Lấy danh sách tất cả module khác (sai)

local wrongModules = {}

for _, mod in ipairs(modules) do

if mod ~= pickedModule then

[Link](wrongModules, mod)

end

end

-- Lấy ngẫu nhiên 3 đáp án sai

local wrongChoices = {}

while #wrongChoices < 3 and #wrongModules > 0 do

local index = [Link](1, #wrongModules)

local mod = [Link](wrongModules, index)

local data = require(mod)

[Link](wrongChoices, {

Name = [Link],
ImageId = [Link] or ""

})

end

-- Gộp đáp án Correct + sai rồi trộn

local allChoices = {

{ Name = [Link], ImageId = [Link] or "" }

for _, wrong in ipairs(wrongChoices) do

[Link](allChoices, wrong)

end

-- Trộn ngẫu nhiên

local function shuffle(t)

local shuffled, used = {}, {}

while #shuffled < #t do

local i = [Link](1, #t)

if not used[i] then

[Link](shuffled, t[i])

used[i] = true

end

end

return shuffled

end

return {
songName = [Link],

soundId = [Link],

choices = shuffle(allChoices)

end

local function disableCharacterMovement(character)

local humanoid = character:FindFirstChildOfClass("Humanoid")

if humanoid then [Link] = 0; [Link] = 0 end

end

local function roll30Bonus()

-- r đồng đều [0,1)

local r = [Link]()

-- bias về thấp: exponent >1 → tập trung vào các giá trị nhỏ

local biased = r ^ 3

-- scale lên [100,999]

local minV, maxV = 100, 999

return [Link](minV + (maxV - minV) * biased + 0.5)

end

-- Lava damage on touch

[Link]:Connect(function(hit)

local character = [Link]

local humanoid = character and character:FindFirstChildOfClass("Humanoid")

local player = humanoid and Players:GetPlayerFromCharacter(character)


if player and humanoid and not burning[humanoid] then

-- Nếu player không trong trận thì bỏ qua

if not player:GetAttribute("InGame") then return end

-- Nếu player đang được miễn damage thì không bắt đầu loop

if player:GetAttribute("ImmuneLava") then

return

end

burning[humanoid] = true

[Link](function()

while burning[humanoid] and [Link] > 0 do

-- cập nhật player từ humanoid (an toàn nếu player respawn)

local p = Players:GetPlayerFromCharacter([Link])

if not p or not p:GetAttribute("InGame") then

break

end

-- Nếu đang miễn damage thì skip lần này (đợi cho tới khi attribute
false)

if p:GetAttribute("ImmuneLava") then

-- chờ 0.2s rồi tiếp tục vòng lặp, không gây damage

[Link](0.2)

else

humanoid:TakeDamage(DAMAGE_PER_SECOND)

[Link](1)
end

end

burning[humanoid] = nil

end)

end

end)

[Link]:Connect(function(hit)

local humanoid = [Link] and [Link]:FindFirstChildOfClass("Humanoid")

if humanoid then

burning[humanoid] = nil

end

end)

-- Lobby countdown (non-blocking)

local function startLobbyCountdown(sec)

for i = sec, 1, -1 do

print("Countdown:", i)

lobbyTimerEvent:FireAllClients(i)

wait(1)

end

lobbyTimerEvent:FireAllClients(0)

end

-- Lobby waiting

local function lobbyWait()

local startTime = tick()

while tick() - startTime < LOBBY_MAX_WAIT do


if #Players:GetPlayers() >= LOBBY_MIN_PLAYERS then break end

wait(1)

end

startLobbyCountdown(START_COUNTDOWN_TIME)

end

-- Arrange platforms and start game

graceTime = graceTime or {}

local function startGame()

gameStarted = true

print("[StartGame] Bắt đầu trận")

for _, player in ipairs(Players:GetPlayers()) do

if not player:GetAttribute("IsAFK") then

-- Đảm bảo platform tồn tại cho player này

local platform = playerPlatformMap[[Link]]

if not platform or not [Link] then

platform = ensurePlatformForPlayer(player)

end

if platform and [Link] then

local hrp = [Link]:FindFirstChild("HumanoidRootPart")

or [Link]:WaitForChild("HumanoidRootPart", 3)

if hrp then
local humanoid =
[Link]:FindFirstChildOfClass("Humanoid")

if humanoid then

humanoid:ChangeState([Link])

end

-- Cho anti-cheat grace time 3s

graceTime[[Link]] = tick() + 3

teleportCharacterToPosition([Link],
[Link])

disableCharacterMovement([Link])

player:SetAttribute("InGame", true)

print(("[StartGame] ✅ Đưa %s vào


trận"):format([Link]))

else

warn(("[StartGame] ❌ %s không có HumanoidRootPart khi


teleport"):format([Link]))

end

else

warn(("[StartGame] ❌ Không có platform cho


%s"):format([Link]))

end

end

end
-- Khóa hồi máu (giữ logic cũ)

for _, player in ipairs(Players:GetPlayers()) do

local char = [Link]

if char then

local humanoid = char:FindFirstChildOfClass("Humanoid")

if humanoid and not humanoid:GetAttribute("NoHeal") then

humanoid:SetAttribute("NoHeal", true)

local lastHealth = [Link]

humanoid:GetPropertyChangedSignal("Health"):Connect(function()

if humanoid:GetAttribute("NoHeal") and [Link]


> lastHealth then

[Link] = lastHealth

else

lastHealth = [Link]

end

end)

end

end

end

-- Gửi trạng thái & scoreboard

gameStateEvent:FireAllClients(true)

local initData = {}

for userId, score in pairs(playerScores) do

local pl = Players:GetPlayerByUserId(userId)
if pl then

[Link](initData, { player = [Link], score = score })

end

end

displayScoreboardEvt:FireAllClients(initData)

end

[Link]:Connect(function(player)

local p = playerPlatformMap[[Link]]

if p then p:Destroy() end

playerPlatformMap[[Link]] = nil

playerScores[[Link]] = nil

answerTimes[[Link]] = nil

hasRecommendedThisMatch[[Link]] = nil

-- đảm bảo tắt attribute

if player and [Link] == nil then

-- không cần pcall

player:SetAttribute("ImmuneLava", false)

end

end)

local function calulateCost(numSongs)

if numSongs == 1 then return 10 end

if numSongs == 2 then return 30 end

if numSongs == 3 then return 45 end

return [Link]
end

local MIN_BOOST = 3.5

local MAX_BOOST = MAX_SAFE_STEP

-- Gắn trước khi any other code, sau khi đã declare all biến (bao gồm gameStarted,
playerPlatformMap, v.v.)

-- Anti-spam AnswerResult

local answerCooldown = {}

local answerSpamCount = {}

local SPAM_LIMIT = 5 -- số lần tối đa trong 1 giây

local COOLDOWN_TIME = 1 -- giây

[Link]:Connect(function(player, result)

local now = tick()

local uid = [Link]

-- Kiểm tra cooldown

if answerCooldown[uid] and now - answerCooldown[uid] < COOLDOWN_TIME then

answerSpamCount[uid] = (answerSpamCount[uid] or 0) + 1

if answerSpamCount[uid] > SPAM_LIMIT then

warn("[AntiCheat] " .. [Link] .. " spam AnswerResult, kick!")

player:Kick("Spam AnswerResult quá mức")

return

end

return -- Bỏ qua do đang cooldown

else

answerCooldown[uid] = now

answerSpamCount[uid] = 0
end

-- Chỉ xử lý nếu player thực sự đang trong game

if not player:GetAttribute("InGame") then

return

end

-- Logic xử lý kết quả câu trả lời (như bạn đang có)

if result == "Correct" then

local timeTaken = tick() - roundStartTime

answerTimes[uid] = timeTaken

incorrectPlayers[uid] = nil

local ls = player:FindFirstChild("leaderstats")

local sv = ls and ls:FindFirstChild("Score")

addScore(player, 1, "AnswerCorrect")

local total = ls and ls:FindFirstChild("Correct")

if total then

[Link] = [Link] + 1

end

local ratio = [Link]((TIME_LIMIT - timeTaken) / TIME_LIMIT, 0, 1)

local boost = MIN_BOOST + (MAX_BOOST - MIN_BOOST) * ratio

tweenPlatformUsingGuide(player, boost)

else

incorrectPlayers[uid] = true
end

totalAnswers += 1

local aliveCount = 0

for _, pl in ipairs(Players:GetPlayers()) do

local h = [Link] and [Link]:FindFirstChildOfClass("Humanoid")

if h and [Link] > 0 then

aliveCount += 1

end

end

if totalAnswers >= aliveCount then

stopMusicEvent:FireAllClients()

end

end)

local function applyPlatformHeightsAndLeaderboard()

local ranking = {}

-- Chỉ lấy người có thời gian trả lời (tức là đã trả lời đúng)

for userIdStr, timeTaken in pairs(answerTimes) do

local userId = tonumber(userIdStr)

local pl = Players:GetPlayerByUserId(userId)

local plat = playerPlatformMap[userId]

if pl and plat and plat:IsDescendantOf(workspace) then

local y = [Link].Y

[Link](ranking, { player = pl, time = timeTaken, y = y })


-- Cập nhật độ cao platform cao nhất (cho podium)

local prevMax = bestSnapshotEver[userId]

if not prevMax or y > prevMax then

bestSnapshotEver[userId] = y

end

end

end

-- ✅ Lưu top3Snapshot theo độ cao platform (phục vụ podium cuối trận)

local rankingByHeight = [Link](ranking) -- tạo bản sao không ảnh hưởng sắp xếp
khác

[Link](rankingByHeight, function(a, b)

return a.y > b.y

end)

top3Snapshot = {}

for i = 1, [Link](3, #rankingByHeight) do

top3Snapshot[i] = rankingByHeight[i]

end

-- ✅ Hiển thị bảng xếp hạng theo thời gian trả lời (ai nhanh nhất đứng đầu)

[Link](ranking, function(a, b)

return [Link] < [Link]

end)

local clientLb = {}

local maxDisplay = 15 -- Giới hạn hiển thị top 15


for i = 1, [Link](maxDisplay, #ranking) do

local e = ranking[i]

[Link](clientLb, {

position = i,

player = [Link],

time = [Link]("%.2f", [Link]),

})

end

displayLeaderboardEvt:FireAllClients(clientLb)

end

local function noOneOnPlatform()

return next(playerPlatformMap) == nil

end

while true do

-- A) Đợi ở lobby

lobbyWait()

-- B) Reset map và tạo platform

resetMap()

-- B1) Chờ tối đa 3 giây để platform tạo xong cho tất cả player không AFK

local ready = false

for i = 1, 30 do -- 30 * 0.1s = 3 giây

local allReady = true

for _, player in ipairs(Players:GetPlayers()) do


if not player:GetAttribute("IsAFK") and not
playerPlatformMap[[Link]] then

allReady = false

break

end

end

if allReady then

ready = true

break

end

[Link](0.1)

end

if not ready then

warn("[PlatformManager] Không thể tạo đủ platform trước khi startGame")

end

-- C) Báo round 0 và bắt đầu game

RoundUpdateEvent:FireAllClients(0)

startGame()

[Link](1)

local gameOver = false

-- ==== D) Vòng chơi chính ====

while not gameOver do

-- 1) Tăng round & update UI


roundCount += 1

for _, plr in ipairs(Players:GetPlayers()) do

plr:SetAttribute("UsedRecommend", false)

end

RoundUpdateEvent:FireAllClients(roundCount)

totalAnswers = 0

-- 1.1) Thưởng BonusRound

if roundCount == 11 then

for _, plr in ipairs(Players:GetPlayers()) do

if isPlayerAliveAndOnPlatform(plr) and not


plr:GetAttribute("Got10Bonus") then

plr:SetAttribute("Got10Bonus", true)

addScore(plr, 25, "BonusRound")

ScoreRewardEvent:FireClient(plr, 25)

end

end

elseif roundCount == 21 then

for _, plr in ipairs(Players:GetPlayers()) do

if isPlayerAliveAndOnPlatform(plr) and not


plr:GetAttribute("Got20Bonus") then

plr:SetAttribute("Got20Bonus", true)

addScore(plr, 50, "BonusRound")

ScoreRewardEvent:FireClient(plr, 50)

end

end

elseif roundCount == 31 then


for _, plr in ipairs(Players:GetPlayers()) do

if isPlayerAliveAndOnPlatform(plr) and not


plr:GetAttribute("Got30Bonus") then

plr:SetAttribute("Got30Bonus", true)

local bonus = roll30Bonus()

addScore(plr, bonus, "BonusRound")

ScoreRewardEvent:FireClient(plr, bonus)

end

end

gameOver = true

end

-- 2) Lava nâng cao

local currentY = [Link].Y

local stepBase = LAVA_STEP_PER_ROUND + STEP_INCREMENT *


[Link](roundCount / 5)

local targetY

if roundCount < 10 then

targetY = currentY + stepBase

elseif roundCount == 10 then

local minY = [Link]

for _, plat in pairs(playerPlatformMap) do

minY = [Link](minY, [Link].Y)

end

local mid = (minY - currentY) * 0.5

targetY = currentY + [Link](mid, stepBase, stepBase * 2)


elseif roundCount % 5 == 0 then

local minY = [Link]

for _, plat in pairs(playerPlatformMap) do

minY = [Link](minY, [Link].Y)

end

targetY = [Link](currentY + stepBase, minY - 5)

else

targetY = currentY + stepBase

end

local lavaStartY = [Link].Y

local tweenStartTime = tick()

TweenService:Create(lavaPart, tweenInfo, {

Position = [Link]([Link].X, targetY, [Link].Z)

}):Play()

[Link](TWEEN_TIME)

if tick() - tweenStartTime > 20 and [Link]([Link].Y - targetY) > 1


then

warn("[PlatformManager] Lava tween bị kẹt! Reset vòng để tránh đóng


băng.")

resetMap()

break

end

checkLavaKill()

roundStartTime = tick()
-- 3) Gửi câu hỏi + phát nhạc

local question = getRandomQuestion()

for _, pl in ipairs(Players:GetPlayers()) do

sendQuestionEvent:FireClient(pl, question)

end

local oldSound = workspace:FindFirstChild("CurrentSong")

if oldSound then oldSound:Destroy() end

local newSound = [Link]("Sound")

[Link] = "CurrentSong"

[Link] = [Link]

[Link] = 1

[Link] = false

[Link] = workspace

newSound:Play()

-- 4) Chờ trả lời

local elapsed = 0

while elapsed < TIME_LIMIT do

if noOneOnPlatform() then

gameOver = true

break

end

[Link](1)

elapsed += 1

end
-- 5) Show leaderboard

applyPlatformHeightsAndLeaderboard()

[Link](2)

answerTimes = {}

incorrectPlayers = {}

checkAntiCheatPosition()

-- 6) Delay rồi kiểm tra tiếp

[Link](5)

if noOneOnPlatform() then

gameOver = true

end

end -- end while not gameOver

-- ==== E) Trận kết thúc: chờ 5s để client xem kết quả rồi sang lobby mới ====

for _, plr in ipairs(Players:GetPlayers()) do

local char = [Link]

local hum = char and char:FindFirstChildOfClass("Humanoid")

local root = char and char:FindFirstChild("HumanoidRootPart")

-- Đặt trạng thái InGame về false NGAY để tránh các script khác chặn

plr:SetAttribute("InGame", false)

if hum and root then

-- Nếu còn sống thì hồi máu

if [Link] > 0 then


[Link] = [Link]

end

-- Gỡ mọi ràng buộc vật lý còn sót

for _, obj in ipairs(root:GetChildren()) do

if obj:IsA("AlignPosition") or obj:IsA("AlignOrientation")

or obj:IsA("BodyGyro") or obj:IsA("BodyPosition")

or obj:IsA("BodyVelocity") or obj:IsA("VectorForce")

or obj:IsA("LinearVelocity") or obj:IsA("AngularVelocity")
then

obj:Destroy()

end

end

-- Reset trạng thái vật lý

[Link] = false

[Link] = false

[Link] = false

[Link] = true

-- Reset chỉ số di chuyển mặc định

[Link] = 16

[Link] = 50

[Link] = 7.2 -- mặc định Roblox

-- Đặt lại vị trí


[Link] = [Link]([Link] + [Link](0, 10, 0))

-- Gỡ NoHeal

hum:SetAttribute("NoHeal", false)

end

end

-- 1) Tính top 3 người cao nhất toàn trận dựa trên bestSnapshotEver

print("[Podium] Bắt đầu vinh danh top 3")

local bestList = {}

for userId, y in pairs(bestSnapshotEver) do

local pl = Players:GetPlayerByUserId(userId)

if pl then

[Link](bestList, { player = pl, y = y })

end

end

-- 2) Sort giảm dần

[Link](bestList, function(a,b) return a.y > b.y end)

-- 3) Lấy top 3

local podiumEntries = {}

for i = 1, [Link](3, #bestList) do

podiumEntries[i] = bestList[i]

end
-- 4) Chuẩn bị danh sách dummy

local podiumDummies = {

workspace:FindFirstChild("Podium1"),

workspace:FindFirstChild("Podium2"),

workspace:FindFirstChild("Podium3"),

-- 4.1) Reset toàn bộ trang phục cũ trên dummy

for _, dummy in ipairs(podiumDummies) do

if dummy then

for _, child in ipairs(dummy:GetChildren()) do

if child:IsA("Accessory")

or child:IsA("CharacterMesh")

or child:IsA("Shirt")

or child:IsA("Pants")

or child:IsA("BodyColors")

or child:IsA("Decal") then

child:Destroy()

end

end

end

end

-- 4.2) Clone appearance cho top 3

for i, entry in ipairs(podiumEntries) do

local pl = [Link]
local dummy = podiumDummies[i]

if pl and dummy then

print([Link]("[Podium] Gán model %s vào %s (height=%.2f)",


[Link], [Link], entry.y))

cloneAppearanceToDummy(pl, dummy)

else

warn([Link]("[Podium] Bỏ qua vị trí %d do thiếu dữ liệu", i))

end

end

-- Cuối cùng reset các biến của trận

answerTimes = {}

incorrectPlayers = {}

bestSnapshotEver = {}

playerPlatformMap = {}

mapInitialized = false

wait(5)

end -- end while true

You might also like