it did make my badge checking system much faster though ty
Wow this is really nice to see! Would something like this ever come out for MarketPlaceService for things like gamepasses?
Wow! This is a nice feature to have. The current game I co-own has 30 badges, so this could simplify things.
I can check up to 50 badges per minute per player based on rate limits. If combined with checking individual badges, you could do 50 + 35 = 85 badge checks per minute per player (In theory).
However, I do believe the limit of 10 should be increased to a number that popular games tend to have, which is at least 30 - 50 badges. This would also make it friendlier for new developers with limited scripting experience
Win Update we created a custom system through our VPS so that’s one lesser costs. If we didn’t use it for other things as well. But i’m so happy with this. As our games are well known in the badge community for having a high amount of badges but also often troubles with loading it in with games that don’t have our custom system yet.
Edit: Seems like we can’t request multiple badge infos at once as well, so we can’t use it (according to the developer who handles this)
meshpart users when they have to change MeshID using a script:
Thank you! The fact that something like this didn’t exist before was definitely surprising, but better late than never. Excited to try out this new API!
On another note, can we expect later support for getting badge information and maybe higher request limits?
I’m slightly annoyed I just rebuilt part of a game I had that used UserHasBadgeAsync to check for owned badges, but oh well. 10 is definitely an improvement, considering they all check simultaneously, but I hope it increases in the future…many badge hunt games, if not also using datastores, won’t find this very helpful.
Haha I just added a feature like that to one of my games; I’m glad to see other people doing it, it seemed like a sensible idea
I just tested this, and I found that the UserHasBadgeAsync limit is 100 per minute, while AwardBadge is 85. I’m also pretty sure that each rate-limit is separate to the other.
I was pretty upset after seeing the maximum ID count, so I went ahead and did a “page” method as a workaround… Hoping this gets increased in the future.
For those who are curious, here you go
local BadgeService = game:GetService("BadgeService")
local TargetUserId = 00000 -- The user Id that you want to scan for badges
local BadgeIdList = {00000,00000,00000,00000,00000} -- Insert badge IDs here
local OwnedBadgesResult = {} -- Final table that returns the status of the badges from the player
-- Generate pages for the function to successfully go through
local Pages = {}
local BadgeTableIndex = 0
local PageIndex = 1
for _,IndexBadgeId in pairs(BadgeIdList) do
if Pages[PageIndex] == nil then -- Create new page
Pages[PageIndex] = {}
end
OwnedBadgesResult[IndexBadgeId] = false
table.insert(Pages[PageIndex],IndexBadgeId)
if #Pages[PageIndex] >= 10 then -- Max page size
PageIndex += 1
end
end
-- Scan through the pages and return results
for _,Page in pairs(Pages) do
local PageResult = BadgeService:CheckUserBadgesAsync(TargetUserId, Page)
for _,IndexResult in pairs(PageResult) do
OwnedBadgesResult[IndexResult] = true
end
end
-- Finished! Check "OwnedBadgesResult"
Yeah, when I think about it, it’s probably an even better solution. You can just load the UserData normally and include an extra BadgesOwned
dictionary within the UserData. This way, there’s no need to make 5-100 API calls when a player joins or for certain Actions, you’d need zero. To award a badge, I simply check if the player doesn’t already own it by looking at the UserData, and directly after awarding a badge I update the UserData and mark the badge as owned
Please keep in mind that your data may become stale if users delete badges or API calls fail as mentioned in my reply here so having a fail-safe is highly advised still.
Thanks alot for the input!
- BadgeService / DatastoreService can go down. If this happens, you’ll be left with stale data.
I actually think this makes relying more on Datastore even more reasonable, as it reduces dependency on BadgeService. I’ve implemented a fallback in case :AwardBadge
fails (as shown in my BadgeHandler function). If BadgeService were to go down in my game, only a few minor features, like a [BETA] chat tag, might be affected if I relied on it. However, if DatastoreService fails, players can’t load their owned items, which makes the game almost unplayable. In that scenario, even a fully functional BadgeService wouldn’t do much to help.
- Players can delete badges from their inventory. Ideally, you should be able to react to this and allow the player to re-earn the badge if they so wish.
I agree that this is an edge case not covered by saving badges in the datastore. But for example, in our game, badges aren’t critical, and we haven’t received any complaints about this issue. I understand that some players might delete badges for cosmetic reasons, to hide that they played a particular game, or maybe for other personal reasons?, which I don’t personally relate to. But there’s even a strange upside to stale data: if someone deletes a badge that can’t be earned anymore (whether by accident or because someone else messed with their account e.g. siblings), they won’t lose the benefits tied to that badge since it’s still marked as earned in the datastore. Another quirky upside is that if players delete badges to hide that they’ve played a particular game, they could re-enter the game without the badges awarding and having to be deleted again.
As for the issue you mentioned in another experience, it might have been a logic mistake by the developer, perhaps they marked the badge as owned before the AwardBadge
call was actually fired.
My Badgehandler Function (it’s not that cleaned up tbh):
local function handle_Badge(Plr, Type, Badge_Id)
local UserId = Plr.UserId
local Saved_Data = ProfileService:Get(Plr, "Badges")
local Badge_Key = tostring(Badge_Id)
if Saved_Data == nil then
warn(script.Name..": SAVED DATA NIL") -- triggers when leaving game in certain moments
return false
end
-- has_Badge
if Type == "has_Badge" then
local Saved_Value = Saved_Data[Badge_Key]
if Saved_Value == nil then
local Result = Retry(function()
return BadgeService:UserHasBadgeAsync(UserId, Badge_Id)
end)
if not Plr.Parent then -- the above yields, so check if ingame
return false
end
if Result == nil then
warn(script.Name.." 1: RESULT == NIL")
return false
end
-- Yield -> Racing Conditions
local Saved_Data = ProfileService:Get(Plr, "Badges")
Saved_Data[Badge_Key] = Result
ProfileService:Set(Plr, "Badges", Saved_Data)
return Result
else
--print("already known")
return Saved_Value
end
elseif Type == "AwardBadge" then
local Result = Retry(function()
return BadgeService:AwardBadge(Plr.UserId, Badge_Id)
end)
--print(Result)
if Result == nil then
warn(script.Name.." 2: RESULT == NIL FOR AWARDING")
end
if not Plr.Parent then -- the above yields, so check if ingame
return false
end
local Saved_Data = ProfileService:Get(Plr, "Badges")
Saved_Data[Badge_Key] = Result -- If successful -> returns true
ProfileService:Set(Plr, "Badges", Saved_Data)
end
-- check if success if already owns badge
-- set_BadgeOwned
end
I’ve just realized that my code might have a potential flaw. The AwardBadge
method returns whether the badge was awarded successfully, so I should be also checking that return value.
However, I’m not entirely sure if this scenario ever occurs, since I remember AwardBadge
typically throwing an error if it fails. But I’m not certain.
Nevermind, I just realized that by pure coincidence that my Retry function, already handles that flaw that I thought my code had:
I believe the game you had the problem in, could’ve also just wrapped it in a pcall and checked whether it failed without checking if the actual AwardBadge Method returned “true”
local function Retry(Function, MaxTries, WaitTime)
local Tries = 0
MaxTries = MaxTries or 5 -- Maximum Retries
WaitTime = WaitTime or 5 -- Wait Time Between Retries
local Success, Value
repeat
Success, Value = pcall(Function)
if not Success then
warn(Value)
Tries = Tries + 1
task.wait(WaitTime)
end
until Tries >= MaxTries or Success
if Success then
return Value
else
return nil
end
end
Also I forgot to include the snippet that awards Badges via BindableEvents:
local function has_Badge(player, badgeId)
-- Check if the player has the badge
return Badge_Handler_BindableFunction:Invoke(player, "has_Badge", badgeId)
end
local function awardBadge(player, badgeId)
-- Check if the player already has the badge
local hasBadge = has_Badge(player, badgeId)
if hasBadge then
return
end
Badge_Handler_BindableFunction:Invoke(player, "AwardBadge", badgeId)
end
That’s an extremely niche use case and if you’re constantly doing that… then that’s probably bad practice. Store your assets in ReplicatedStorage.
I don’t think you’re aware of the ApplyMesh
method:
Does this work too for making a player click another player that open the gui showing the badges of the player that have got?