Help with script

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)

1 Like

What seems to be the problem exactly? Is the UI not showing?

1 Like

the ui shows, it’s the accessories that apprently do not get passed to the client for the list to be populated, i only get "warn(“Error processing mannequin: " … tostring(result))” in the output, but still i can’t figure out why

and what does the result return?

Under for _, accessory in pairs(description:GetAccessories()) do local assetId = accessory.AssetId if assetId and assetId ~= 0 then add a print statement print("[Debug] accessory found: ", assetId)

If that fails then add debuggin to the function getItemInfo(assetId, "Accessory")

	return MarketplaceService:GetProductInfo(assetId, Enum.InfoType.Asset)
end)

if not success then
	warn("[Error] Failed to fetch product info for Asset ID:", assetId, result)
	return nil
end

If that fails aswell Debug getAvatarItemsFromMannequin by wrapping it in a pcall and log its return value so modify this: "local success, result = pcall(function()
return getAvatarItemsFromMannequin(mannequinToFetch)
end)

if success then
local avatarItems = result" to this:

	local items = getAvatarItemsFromMannequin(mannequinToFetch)
	print("[Debug] Items Retrieved:", items)
	return items
end)

now for the finale debug marketplaceEvent:FireClient(player, avatarItems):

if player and player:IsA("Player") then
	print("[Debug] Sending avatar items:", avatarItems)
	marketplaceEvent:FireClient(player, avatarItems)

On the client side jus add this:

marketplaceRemoteEvent.OnClientEvent:Connect(function(avatarItems)
	print("[Debug] Received Avatar Items:", avatarItems)

So this is what it should be printing in the console log:

[Debug] Accessory Found: ... → If this doesn’t print, GetAccessories() is failing.
[Error] Failed to fetch product info → If this shows, MarketplaceService:GetProductInfo() is failing.
[Debug] Items Retrieved: → If this is {}, the issue is in getAvatarItemsFromMannequin().
[Debug] Sending avatar items: → If empty, something failed before sending.
[Debug] Received Avatar Items: on the client-side.
1 Like

thank you all guys for your time, but i managed to rewrite the whole system and it now works.

1 Like