So I followed a tutorial and got a model and customised everything, but the it only updates a players stats when they are in-game. E.G: There is a hacker ingame that has a insane amount of “coins”. A admin sees this on the global leaderboard and resets their data using data:RemoveAsync() the hacker gets banned and their data gets reset. But my leaderboard script only updates when the player to update is playing the game.
Script:
wait(1)
-- [ SETTINGS ] --
local statsName = "emeralds" -- Your stats name
local maxItems = 50 -- Max number of items to be displayed on the leaderboard
local minValueDisplay = 30 -- Any numbers lower than this will be excluded
local maxValueDisplay = 10e15 -- (10 ^ 15) Any numbers higher than this will be excluded
local abbreviateValue = false -- The displayed number gets abbreviated to make it "human readable"
local updateEvery = 10 -- (in seconds) How often the leaderboard has to update
local headingColor = Color3.fromRGB(0, 169, 15) -- The background color of the heading
-- [ END SETTINGS ] --
-- Don't edit if you don't know what you're doing --
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local DataStore = DataStoreService:GetOrderedDataStore("LeaderstatsData" .. statsName)
local Frame = script.Parent.Frame
local Contents = Frame.Contents
local Template = script.objTemplate
local COLORS = {
Default = Color3.fromRGB(38, 50, 56),
Gold = Color3.fromRGB(255, 215, 0),
Silver = Color3.fromRGB(192, 192, 192),
Bronze = Color3.fromRGB(205, 127, 50)
}
local ABBREVIATIONS = { "K", "M", "B", "T" }
local function toHumanReadableNumber(num)
if num < 1000 then
return tostring(num)
end
local digits = math.floor(math.log10(num)) + 1
local index = math.min(#ABBREVIATIONS, math.floor((digits - 1) / 3))
local front = num / math.pow(10, index * 3)
return string.format("%i%s+", front, ABBREVIATIONS[index])
end
local function getItems()
local data = DataStore:GetSortedAsync(false, maxItems, minValueDisplay, maxValueDisplay)
local topPage = data:GetCurrentPage()
Contents.Items.Nothing.Visible = #topPage == 0 and true or false
for position, v in ipairs(topPage) do
local userId = v.key
local value = v.value
local username = "[Not Available]"
local color = COLORS.Default
local success, err = pcall(function()
username = Players:GetNameFromUserIdAsync(userId)
end)
if position == 1 then
color = COLORS.Gold
elseif position == 2 then
color = COLORS.Silver
elseif position == 3 then
color = COLORS.Bronze
end
local item = Template:Clone()
item.Name = username
item.LayoutOrder = position
item.Values.Number.TextColor3 = color
item.Values.Number.Text = position
item.Values.Username.Text = username
item.Values.Value.Text = abbreviateValue and toHumanReadableNumber(value) or value
item.Parent = Contents.Items
end
end
script.Parent.Parent.Color = headingColor
Frame.Heading.ImageColor3 = headingColor
Frame.Heading.Bar.BackgroundColor3 = headingColor
while true do
for _, player in pairs(Players:GetPlayers()) do
local leaderstats = player:FindFirstChild("leaderstats")
if not leaderstats then
warn("Couldn't find leaderstats!")
break
end
local statsValue = leaderstats:FindFirstChild(statsName)
if not statsValue then
warn("Couldn't find " .. statsName .. " in leaderstats!")
break
end
pcall(function()
DataStore:UpdateAsync(player.UserId, function()
return tonumber(statsValue.Value)
end)
end)
end
for _, item in pairs(Contents.Items:GetChildren()) do
if item:IsA("Frame") then
item:Destroy()
end
end
getItems()
wait()
Frame.Heading.Heading.Text = "THE VILLAGERS"
Contents.GuideTopBar.Value.Text = statsName
wait(updateEvery)
end
if theres only a few people on the leaderboard you can delete the hackers manually with any datastore edit plugin or with some command line snippet
if you want to do it in the script then use the ids of the players that you get from :GetStortedAsync to reconstruct their datstore key and then save their current data to the ordered data store
If you find the script which admins use to ban people, you can remove the data store entry automatically through there with DataStore:RemoveAsync(). This makes it so any hackers will just have their data removed from the leaderboard.
This can also be possible if you have some kind of external admin panel which uses the HTTP OpenCloud API.
Basically, as long as you can access the data store, it’s doable automatically.
ProfileService uses Roblox data stores. An OrderedDataStore is the best option here.
But isn’t OrderedDataStore also similar to GlobalDataStore? Wouldn’t ProfileService be more secure instead of using GlobalDataStore, as it has provisions in place to prevent data loss in some cases?
GlobalDataStore is a class which all data store variants inherit from.
Since this is a leaderboard, it only needs to mimic player data, i.e. if saving fails it’ll just be updated to the new value next save. We don’t really need to worry about data loss in the leaderboard for this reason, just using a standard SetAsync with a pcall should be fine.
If it was the actual player data, yes, we would need to be much more careful to ensure data saves properly, which is where things like session locking and data versioning come into play. ProfileService would be good for that, but bare in mind it mainly puts in place features which people don’t know how to/are not confident enough to put in themselves, so they use ProfileService which does it for them. Your data store system could be as secure if not more secure than ProfileService if you use the APIs and data stores right.
But do you use it for the leaderboard entries? RemoteAsync deletes the latest entry, so using OrderedDataStore:RemoveAsync() for your leaderboard entries should get rid of the ones you want to remove.
If you want to remove their entry whilst they are offline, you will need the data store entry key. This is the key you save data with in the OrderedDataStore, which should include their UserId as well. If you want their UserId, you can use Players:GetUserIdFromNameAsync() and remove it using that.
print("Reset Emeralds command requested and recieved.")
print("Info: Moderator: "..player.Name..". Requested Emerald Reset on: "..value..".")
local DataStoreService = game:GetService("DataStoreService")
local leaderstatsData = DataStoreService:GetDataStore("LeaderstatsData")
local foundPlayerID = game.Players:GetUserIdFromNameAsync(value)
if foundPlayerID then
print("Username has been identified as legitimate player. Found player ID: "..foundPlayerID)
game.Players:BanAsync({
UserIds = {foundPlayerID},
ApplyToUniverse = true,
Duration = 30,
DisplayReason = "Your emeralds were reset. Your data is processsing. This should take around 30 seconds.",
PrivateReason = "User has been emerald resetted by @"..player.Name,
ExcludeAltAccounts = true
})
wait(1.3)
leaderstatsData:RemoveAsync(tostring(foundPlayerID))
yep, you’re not removing the player’s data from the leaderboard, only the leaderstats data store. You can use GetOrderedDataStore for the leaderboard name and use RemoveAsync on that.
There seems to be a lot of network request functions here. Are these run in a pcall?
You see how at the top of the leaderboard script, there’s a DataStoreService:GetOrderedDataStore() call? You need to remove data from that data store when you ban a player.
local leaderboard = DataStoreService:GetOrderedDataStore("LeaderstatsData_emeralds")
leaderboard:RemoveAsync(tostring(foundPlayerId))