context: the player triggers a proximityprompt on a mannequin, a ui pops up on the client displaying all the items the mannequin is wearing and allows for single/bulk purchase.
the scripts are kinda long so i apologize in advance, it’s the first time for me doing something like this and if anyone has any suggestions/fixes because i can’t get this to fully work.
proximityprompt script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local client = ReplicatedStorage:FindFirstChild("Client")
local bindableEvent = client:FindFirstChild("ProximityPrompt")
script.Parent.Triggered:Connect(function(player)
local mannequinName = script.Parent.Parent.Parent.Name
local success, errMsg = pcall(function()
bindableEvent:Fire(player, mannequinName)
end)
if not success then
warn("[Error] Failed to fire ProximityPrompt event:", errMsg)
end
end)
server script
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MarketplaceService = game:GetService("MarketplaceService")
local client = ReplicatedStorage:WaitForChild("Client")
local marketplaceEvent = client:WaitForChild("Marketplace")
local promptPurchaseEvent = client:WaitForChild("PromptPurchase")
local notifyClientEvent = client:WaitForChild("NotifyClient")
local proximityPromptEvent = client:WaitForChild("ProximityPrompt")
local requestsPerMinute = 60
local requestCounter = 0
local lastReset = os.time()
local itemCache = {}
local function checkRateLimit()
local currentTime = os.time()
if currentTime - lastReset >= 60 then
requestCounter = 0
lastReset = currentTime
end
if requestCounter >= requestsPerMinute then
return false
end
requestCounter += 1
return true
end
local function getItemInfo(assetId, assetType)
local cacheKey = assetId .. "_" .. (assetType or "")
if itemCache[cacheKey] then
return itemCache[cacheKey]
end
if not checkRateLimit() then
task.wait(1)
return getItemInfo(assetId, assetType)
end
local success, result = pcall(function()
return MarketplaceService:GetProductInfo(assetId, Enum.InfoType.Asset)
end)
if success and result then
local itemData = {
id = assetId,
name = result.Name,
price = result.PriceInRobux or 0,
type = assetType or "Unknown",
bundleId = result.BundleId
}
itemCache[cacheKey] = itemData
return itemData
else
warn("Failed to get product info for asset ID " .. assetId .. ": " .. tostring(result))
return nil
end
end
local function getBundleInfo(bundleId)
local cacheKey = "bundle_" .. bundleId
if itemCache[cacheKey] then
return itemCache[cacheKey]
end
if not checkRateLimit() then
task.wait(1)
return getBundleInfo(bundleId)
end
local success, result = pcall(function()
return MarketplaceService:GetBundleDetails(bundleId)
end)
if success and result then
local bundleData = {
id = bundleId,
name = result.Name,
price = result.PriceInRobux or 0,
type = "Bundle",
description = result.Description,
bundleType = result.BundleType.Name
}
pcall(function()
bundleData.thumbnailId = result.BundleCardImageAssetId
end)
itemCache[cacheKey] = bundleData
return bundleData
else
warn("Failed to get bundle info for bundle ID " .. bundleId)
return nil
end
end
local function getAnimationPackInfo(animPackId)
local cacheKey = "animpack_" .. animPackId
if itemCache[cacheKey] then
return itemCache[cacheKey]
end
local bundleInfo = getBundleInfo(animPackId)
if bundleInfo then
bundleInfo.type = "AnimationPack"
itemCache[cacheKey] = bundleInfo
return bundleInfo
end
local assetInfo = getItemInfo(animPackId, "AnimationPack")
if assetInfo then
itemCache[cacheKey] = assetInfo
return assetInfo
end
return nil
end
local animationPackTypes = {
["Zombie"] = 616117076,
["Werewolf"] = 1083439238,
["Vampire"] = 1083439355,
["Robot"] = 616117419,
["Pirate"] = 750781874,
["Ninja"] = 656117400,
["Mage"] = 707742142,
["Levitation"] = 616006778,
["Knight"] = 658831500,
["Elder"] = 658832070,
["Bubbly"] = 891621366,
["Astronaut"] = 891609353,
["Toy"] = 782841498,
["Superhero"] = 782841498,
["Stylish"] = 616136790,
["Oldschool"] = 616136999,
["Popstar"] = 1212900985,
["Patrol"] = 1212954578,
["Sneaky"] = 1212980303,
["Princess"] = 1212641309,
["Confident"] = 1069946257,
["Cartoony"] = 1212980348,
["Cowboy"] = 1212954642,
["Ghost"] = 1484581387,
["Mummy"] = 1484581536
}
local function getAvatarItemsFromMannequin(mannequin)
if not mannequin or not mannequin:IsA("Model") or not mannequin:FindFirstChildOfClass("Humanoid") then
warn("Invalid mannequin provided")
return {
accessories = {},
clothing = {},
animationPacks = {},
bodyParts = {},
bundles = {},
ugcItems = {}
}
end
local humanoid = mannequin:FindFirstChildOfClass("Humanoid")
local description = humanoid:GetAppliedDescription()
local avatarItems = {
accessories = {},
clothing = {},
animationPacks = {},
bodyParts = {},
bundles = {},
ugcItems = {}
}
local processedBundles = {}
for _, accessory in pairs(description:GetAccessories()) do
local assetId = accessory.AssetId
if assetId and assetId ~= 0 then
local accessoryInfo = getItemInfo(assetId, "Accessory")
if accessoryInfo then
if accessoryInfo.bundleId and not processedBundles[accessoryInfo.bundleId] then
local bundleInfo = getBundleInfo(accessoryInfo.bundleId)
if bundleInfo then
table.insert(avatarItems.bundles, bundleInfo)
processedBundles[accessoryInfo.bundleId] = true
end
else
table.insert(avatarItems.accessories, accessoryInfo)
end
end
end
end
if description.ShirtTemplate ~= "" then
local shirtId = tonumber(description.ShirtTemplate:match("%d+"))
if shirtId then
local shirtInfo = getItemInfo(shirtId, "Shirt")
if shirtInfo then
if shirtInfo.bundleId and not processedBundles[shirtInfo.bundleId] then
local bundleInfo = getBundleInfo(shirtInfo.bundleId)
if bundleInfo then
table.insert(avatarItems.bundles, bundleInfo)
processedBundles[shirtInfo.bundleId] = true
end
else
table.insert(avatarItems.clothing, shirtInfo)
end
end
end
end
if description.PantsTemplate ~= "" then
local pantsId = tonumber(description.PantsTemplate:match("%d+"))
if pantsId then
local pantsInfo = getItemInfo(pantsId, "Pants")
if pantsInfo then
if pantsInfo.bundleId and not processedBundles[pantsInfo.bundleId] then
local bundleInfo = getBundleInfo(pantsInfo.bundleId)
if bundleInfo then
table.insert(avatarItems.bundles, bundleInfo)
processedBundles[pantsInfo.bundleId] = true
end
else
table.insert(avatarItems.clothing, pantsInfo)
end
end
end
end
local animator = humanoid:FindFirstChildOfClass("Animator")
if animator then
local addedPacks = {}
for _, track in pairs(animator:GetPlayingAnimationTracks()) do
local animName = track.Animation.Name
for packName, packId in pairs(animationPackTypes) do
if string.find(animName, packName) and not addedPacks[packId] then
local packInfo = getAnimationPackInfo(packId)
if packInfo then
if packInfo.type == "Bundle" then
if not processedBundles[packId] then
table.insert(avatarItems.bundles, packInfo)
processedBundles[packId] = true
end
else
table.insert(avatarItems.animationPacks, packInfo)
end
addedPacks[packId] = true
end
break
end
end
end
end
if description.Face ~= 0 then
local faceInfo = getItemInfo(description.Face, "Face")
if faceInfo then
if faceInfo.bundleId and not processedBundles[faceInfo.bundleId] then
local bundleInfo = getBundleInfo(faceInfo.bundleId)
if bundleInfo then
table.insert(avatarItems.bundles, bundleInfo)
processedBundles[faceInfo.bundleId] = true
end
else
table.insert(avatarItems.bodyParts, faceInfo)
end
end
end
if description.Head ~= 0 then
local headInfo = getItemInfo(description.Head, "Head")
if headInfo then
if headInfo.bundleId and not processedBundles[headInfo.bundleId] then
local bundleInfo = getBundleInfo(headInfo.bundleId)
if bundleInfo then
table.insert(avatarItems.bundles, bundleInfo)
processedBundles[headInfo.bundleId] = true
end
else
table.insert(avatarItems.bodyParts, headInfo)
end
end
end
pcall(function()
for _, emote in pairs(description:GetEquippedEmotes()) do
if emote.AssetId then
local emoteInfo = getItemInfo(emote.AssetId, "Emote")
if emoteInfo and emoteInfo.bundleId and not processedBundles[emoteInfo.bundleId] then
local bundleInfo = getBundleInfo(emoteInfo.bundleId)
if bundleInfo then
table.insert(avatarItems.bundles, bundleInfo)
processedBundles[emoteInfo.bundleId] = true
end
end
end
end
end)
for _, instance in pairs(mannequin:GetDescendants()) do
if instance:IsA("Accessory") and not instance:GetAttribute("Processed") then
local assetId = instance:GetAttribute("AssetId") or 0
if assetId ~= 0 then
local itemInfo = getItemInfo(assetId, "UGC")
if itemInfo then
if itemInfo.bundleId and not processedBundles[itemInfo.bundleId] then
local bundleInfo = getBundleInfo(itemInfo.bundleId)
if bundleInfo then
table.insert(avatarItems.bundles, bundleInfo)
processedBundles[itemInfo.bundleId] = true
end
else
table.insert(avatarItems.ugcItems, itemInfo)
end
end
instance:SetAttribute("Processed", true)
end
end
end
task.wait(0.1)
return avatarItems
end
proximityPromptEvent.Event:Connect(function(player, mannequinName)
if not player or not player:IsA("Player") then
warn("Invalid player reference")
return
end
local mannequinToFetch = workspace:FindFirstChild("Mannequins"):FindFirstChild(mannequinName)
if not mannequinToFetch then
warn("Mannequin not found: " .. mannequinName)
marketplaceEvent:FireClient(player, {})
notifyClientEvent:FireClient(player, "Mannequin not found", false)
return
end
local success, result = pcall(function()
return getAvatarItemsFromMannequin(mannequinToFetch)
end)
if success then
local avatarItems = result
if player and player:IsA("Player") then
print("Sending avatar items to player " .. player.Name)
marketplaceEvent:FireClient(player, avatarItems)
else
warn("Invalid player object when trying to fire client event")
end
else
warn("Error processing mannequin: " .. tostring(result))
if player and player:IsA("Player") then
marketplaceEvent:FireClient(player, {})
end
end
end)
promptPurchaseEvent.OnServerEvent:Connect(function(player, bulkPurchaseAssetsTable)
if type(bulkPurchaseAssetsTable) ~= "table" then
warn("Invalid bulk purchase data received from " .. player.Name)
notifyClientEvent:FireClient(player, "Invalid purchase data", false)
return
end
local bundlesToPurchase = {}
local assetsToPurchase = {}
for _, item in ipairs(bulkPurchaseAssetsTable) do
if item.Type == "Bundle" then
table.insert(bundlesToPurchase, item.Id)
else
table.insert(assetsToPurchase, item.Id)
end
end
for _, bundleId in ipairs(bundlesToPurchase) do
pcall(function()
MarketplaceService:PromptBundlePurchase(player, bundleId)
end)
task.wait(0.5)
end
for _, assetId in ipairs(assetsToPurchase) do
pcall(function()
MarketplaceService:PromptPurchase(player, assetId)
end)
task.wait(0.5)
end
notifyClientEvent:FireClient(player, "Purchase prompts sent", true)
end)
client sided script
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MarketplaceService = game:GetService("MarketplaceService")
local player = Players.LocalPlayer
local client = ReplicatedStorage:WaitForChild("Client")
local marketplaceRemoteEvent = client:WaitForChild("Marketplace")
local promptPurchaseRemoteEvent = client:WaitForChild("PromptPurchase")
local proximityPromptEvent = client:WaitForChild("ProximityPrompt")
local list = script.Parent:WaitForChild("Container"):WaitForChild("List")
local bulkPurchaseToggle = list.Parent:WaitForChild("BulkPurchaseToggle")
local template = script:WaitForChild("Template")
local function playerOwnsAsset(assetId)
local success, result = pcall(function()
return MarketplaceService:PlayerOwnsAsset(player, assetId)
end)
return success and result or false
end
local function createItemFromTemplate(data)
local assetTemplate = template:Clone()
assetTemplate.Name = "Asset" .. data.id
assetTemplate.Parent = list
local assetId = assetTemplate:WaitForChild("AssetId")
local toggle, icon, notOwned, owned, name, price =
assetTemplate:WaitForChild("Toggle"),
assetTemplate:WaitForChild("Icon"),
assetTemplate:WaitForChild("NotOwned"),
assetTemplate:WaitForChild("Owned"),
assetTemplate:WaitForChild("Name"),
assetTemplate:WaitForChild("Price")
if playerOwnsAsset(data.id) then
owned.Visible = true
notOwned.Visible = false
else
owned.Visible = false
notOwned.Visible = true
end
icon.Image = "rbxassetid://" .. data.id
price.Text = data.price == 0 and "Free" or tostring(data.price) .. " Robux"
name.Text = data.name
assetId.Value = data.id
assetTemplate.Visible = true
return assetTemplate
end
local function populateList(avatarItems)
for _, child in pairs(list:GetChildren()) do
if child:IsA("Frame") and child.Name:match("^Asset") then
child:Destroy()
end
end
for _, category in pairs({"accessories", "clothing", "animationPacks", "bodyParts", "bundles"}) do
if avatarItems[category] then
for _, item in ipairs(avatarItems[category]) do
createItemFromTemplate(item)
end
end
end
end
local function setupBulkPurchaseButton()
bulkPurchaseToggle.MouseButton1Click:Connect(function()
local bulkPurchaseAssetsTable = {}
for _, child in pairs(list:GetChildren()) do
if child:IsA("Frame") and child.Name:match("^Asset") then
local toggleButton = child:FindFirstChild("Toggle")
local assetIdValue = child:FindFirstChild("AssetId").Value
if toggleButton and assetIdValue and not playerOwnsAsset(assetIdValue) then
table.insert(bulkPurchaseAssetsTable, {Type = Enum.AvatarAssetType.AssetType, Id = assetIdValue})
end
end
end
if #bulkPurchaseAssetsTable > 0 then
promptPurchaseRemoteEvent:FireServer(bulkPurchaseAssetsTable)
end
end)
end
marketplaceRemoteEvent.OnClientEvent:Connect(function(avatarItems)
if type(avatarItems) == "table" then
populateList(avatarItems)
setupBulkPurchaseButton()
local blurEffect = Instance.new("BlurEffect", game:GetService("Lighting"))
blurEffect.Name = "UI"
blurEffect.Size = 25
player:WaitForChild("PlayerGui"):WaitForChild("Disclaimer"):WaitForChild("BulkPurchase").Visible = true
else
warn("Invalid avatar items received from the server.")
end
end)