Accessory-Type Identifiers for Accessories

As a Roblox developer, it is currently too hard to figure out what kind of accessory(Hat, Hair, etc.) a certain accessory is.

If Roblox is able to address this issue, it would improve my game / my development experience because it’d allow Developers to offer the player more freedom over how their character looks while still limiting some options.

For example, if I was working on a character creation UI, as of now, I would have to offer a limited selection of hairs for the player to choose from, which may not include the player’s desired hear. With some kind of identifier on accessories telling us what each one is, I would be able to allow the player to wear whatever hair they want so long as they have it equipped on their avatar.

9 Likes

What is the specific API context in which you looking to retrieve an accessory’s type?

As far as I am aware, there are already solutions in at least two contexts:

(1) You have a reference to the Instance:

local function getAccessoryType(accessoryInstance)
    local attachment = accessoryInstance.Handle:FindFirstChildOfClass("Attachment")
    local accessoryType = attachment.Name:match("(.+)Attachment")
    return accessoryType
end

(2) You have the AssetId:

-- https://developer.roblox.com/en-us/api-reference/enum/AssetType
local assetTypeLookup = {
    [41] = "Hair";
    [42] = "Face";
    [43] = "Neck";
    [44] = "Shoulder";
    [45] = "Front";
    [46] = "Back";
    [47] = "Waist";
}

local marketplaceService = game:GetService("MarketplaceService")

local function getAccessoryType(accessoryAssetId)
    local assetInfo = marketplaceService:GetProductInfo(accessoryAssetId, Enum.InfoType.Asset)
    local accessoryType = assetTypeLookup[assetInfo.AssetTypeId]
    return accessoryType
end

I realise you might be aware of both of these techniques - perhaps you’re looking for something less reliant on parsing or hard-coding.

3 Likes

I’ve considered these solutions, but I was hoping for something more reliable. Web calls are prone to fail/throttle sometimes, which can be somewhat impractical for larger games. I was thinking there could be property that is set whenever the hat is loaded in, that way it’s just one request done whenever the player’s character is loaded. The value could probably be cached this way too, saving even more requests.

This method isn’t reliable because some accessory creators don’t use the correct attachment. Shaggy 2.0 is a good example. It uses the hat attachment instead of the hair attachment despite clearly being a hair accessory.

I don’t think there’s a way of solving this consistently without using a web API at the moment.
Sorry for the bump, but I thought I would just share my current workaround for anybody who stumbles across this looking for answers like I was.
Place the following code in a script in game.ServerScriptService or somewhere similar.

local InsertService = game:GetService("InsertService")

game.Players.PlayerAdded:Connect(function(Player : Player)
	Player.CharacterAdded:Connect(function(Character : Model)
		-- this ENTIRE mess is literally just for getting the assetId and AssetType of each accessory the player is wearing.

		-- get all accessories the player is currently wearing and their assetIds
		local appearanceInfo = game.Players:GetCharacterAppearanceInfoAsync(Player.UserId)

		-- itterate through each asset the player is wearing
		for i, assetInfo : AssetInfo in ipairs(appearanceInfo.assets) do
			-- make sure its an accessory and not a body part or a face or something.
			if assetInfo.assetType.name ~= "Hat" and not string.find(assetInfo.assetType.name, "Accessory") then continue end

			-- load in the accessory Roblox claims the player is wearing
			local success, loadedModel = pcall(InsertService.LoadAsset, InsertService, assetInfo.id)
			if not success then
				warn("Something went wrong while trying to load accessory", assetInfo, loadedModel)
				debug.traceback(loadedModel)
				continue
			end

			--print("-------", assetInfo.name, "----------")

			local referenceAccesory : Accessory = loadedModel:FindFirstChildOfClass("Accessory") -- async api calls can fail.
			local referenceHandle : MeshPart? = referenceAccesory:FindFirstChild("Handle")
			local referenceSpecialMesh : SpecialMesh? = nil
			if referenceHandle then referenceSpecialMesh = referenceHandle:FindFirstChildOfClass("SpecialMesh") end

			-- painstakingly compare the imported accessory to every accessory the player is wearing
			for i, wornAccessory in ipairs(Character:GetChildren()) do
				if wornAccessory:IsA("Accessory") then
					local wornHandle : MeshPart? = wornAccessory:FindFirstChild("Handle")
					local wornSpecialMesh : SpecialMesh? = nil
					if wornHandle then wornSpecialMesh = wornHandle:FindFirstChildOfClass("SpecialMesh") end

					local function applyAssetIdToAccessory()
						wornAccessory:SetAttribute("AssetId", assetInfo.id)
						wornAccessory:SetAttribute("AssetType", assetInfo.assetType.name) -- name is lowercase for some reason.
						wornAccessory:SetAttribute("WebName", assetInfo.name)
					end

					--print(wornAccessory, referenceAccesory, wornHandle, referenceHandle, wornSpecialMesh, referenceSpecialMesh)
					if wornAccessory.Name == referenceAccesory.Name and wornHandle and referenceHandle then
						local wornTextureId : number? = (wornHandle and wornHandle:IsA("MeshPart") and wornHandle.TextureID) or (wornSpecialMesh and wornSpecialMesh.TextureId)
						local wornMeshId : number? = (wornHandle and wornHandle:IsA("MeshPart") and wornHandle.MeshId) or (wornSpecialMesh and wornSpecialMesh.MeshId)
						local referenceTextureId : number? = (referenceHandle and referenceHandle:IsA("MeshPart") and referenceHandle.TextureID) or (referenceSpecialMesh and referenceSpecialMesh.TextureId)
						local referenceMeshId : number? = (referenceHandle and referenceHandle:IsA("MeshPart") and referenceHandle.MeshId) or (referenceSpecialMesh and referenceSpecialMesh.MeshId)

						--print("textureIds: ", wornTextureId, referenceTextureId)
						--print("meshIds: ", wornMeshId, referenceTextureId)

						if wornTextureId and referenceTextureId
							and wornTextureId == referenceTextureId
							and wornMeshId and referenceMeshId
							and wornMeshId == referenceMeshId then

							-- these two match. Give the worn accessory attributes for its assetId, assetType, and website name.
							applyAssetIdToAccessory()
						end
					end
				end
			end
		end
	end)
end)

The result:

image

Simply get these values in code with

local assetType = Tool:GetAttribute("AssetType")

Sorry if I bothered anybody.

1 Like