--constants
local PLAYERS = game:GetService("Players")
local TOP_LEVELS_ODS = game:GetService("DataStoreService"):GetOrderedDataStore("TopLevels")
local WAVES_SURVIVED_ODS = game:GetService("DataStoreService"):GetOrderedDataStore("WavesSurvived")
local DONATED_ROBUX_ODS = game:GetService("DataStoreService"):GetOrderedDataStore("DonatedRobux")
local TOP_KILLS_ODS = game:GetService("DataStoreService"):GetOrderedDataStore("TopKills")
local WEEKLY_TOP_KILLS_ODS;
local RE = game.ReplicatedStorage:WaitForChild("RE")
local DATA_MODULE = require(script.Parent.Data)
local ROLE_TAGS_MODULE = require(game.ReplicatedStorage.Modules["Role Tags"])
local GUI = game.ReplicatedStorage:WaitForChild("GUI")
local GLOBAL_LIST_ITEM = GUI:WaitForChild("GlobalListItem")
local WEEKLY_LIST_ITEM = GUI:WaitForChild("WeeklyListItem")
local LOBBY = workspace:WaitForChild("Lobby")
--settings
local UPDATE_CYCLE = 45 --refreshes leaderbord every 10 seconds
local EXCLUDE_ADMINS_AND_ABOVE = false
local SECONDS_IN_WEEK = 60 * 60 * 24 * 7 --60 * 60 * 24 * 7
local WEEK_ONE_START_TIME = 1552262399 --3/10/19 11:59:59 PM UTC https://www.epochconverter.com/
local MAX_WEEKLY_PLACES = 50
--print(SECONDS_IN_WEEK % ((WEEK_ONE_START_TIME + SECONDS_IN_WEEK)- WEEK_ONE_START_TIME))
--body
local lastUpdated = 0
local updating = false
local currentWeek = nil
local caches = {
global = {
level = {},
wavesSurvived = {},
donatedRobux = {},
kills = {}
},
weekly = {
kills = {}
}
}
local LeaderBoardService = {}
LeaderBoardService.__index = LeaderBoardService
--private static functions (bc isnt needed externally)
function FormatCountdownDHMS(seconds)
return string.format("%d:%02d:%02d:%02d", (seconds/86400)%7, (seconds/3600)%24, (seconds/60)%60, seconds%60)
end
--print(FormatCountdownDHMS((60 * 60 * 24 * 8) - 67 ))
--[[
print(string.format("%05d", 23.23))]]
function FormatPlace(place)
if place == 1 then
return "st"
elseif place == 2 then
return "nd"
elseif place == 3 then
return "rd"
else
return "th"
end
end
function AbbreviateNumber(n)
if n > 1000 then
n = (n/1000)
return (math.floor(n/.1+0.5)*.1) .. "K"
elseif n > 1000000 then
n = (n/1000000)
return (math.floor(n/.1+0.5)*.1).. "M"
else
return n .. ""
end
end
function UpdateODS(ODS, valueDir)
for _, player in pairs(PLAYERS:GetPlayers()) do
local plyrData = DATA_MODULE.GetPlayerData(player)
if plyrData then
local value
local adminOrAbove = ROLE_TAGS_MODULE.Check(player, "ADM")
if adminOrAbove and EXCLUDE_ADMINS_AND_ABOVE then
value = 0
else
value = plyrData
for i = 1, #valueDir do
value = value[valueDir[i]]
end
end
if typeof(value) == "number" then --just in case
local success, msg = pcall(function()
ODS:SetAsync(player.UserId, value)
end)
end
end
end
end
function UpdateCache(ODS, cacheType, keyName, pageSize)
if ODS and cacheType[keyName] then
local newCache = {}
--local newInGameTopCache = {}
local success, pages = pcall(function()
return ODS:GetSortedAsync(false, pageSize)
end)
if success and pages then
local currentPage = pages:GetCurrentPage()
for it, item in pairs(currentPage) do
local userID = item.key
--[[for _, player in pairs(PLAYERS:GetPlayers()) do
if tostring(player.UserId) == tostring(userID) then
newInGameTopCache[tostring(player.UserId)] = it
end
end]]
local success, name = pcall(
function()
return PLAYERS:GetNameFromUserIdAsync(userID)
end
)
if success and name then
local val = item.value
if val > 0 then
newCache[it] = {
userID = userID,
name = name,
image = "http://www.roblox.com/Thumbs/Avatar.ashx?x=100&y=100&Format=Png&username=" .. name,
[keyName] = val,
}
end
end
end
cacheType[keyName] = newCache
--inGameTopPlayersCache[cacheName] = newInGameTopCache
end
end
end
--static methods (exposed externally)
function LeaderBoardService.GetWeeklyPlacePrize(place)
local startingPrize = 40000
local manipPrize = 35000
local scale = (MAX_WEEKLY_PLACES - math.clamp(place, 2, MAX_WEEKLY_PLACES)) / (MAX_WEEKLY_PLACES - 2)
local bonusPrize = place == 1 and 25000 or 0
return math.max(0, math.floor((scale * manipPrize) + 0.5) + startingPrize + bonusPrize)
end
function LeaderBoardService.GetCurrentWeek()
return math.floor((os.time() - WEEK_ONE_START_TIME)/SECONDS_IN_WEEK)
end
function LeaderBoardService.UpdateGlobal()
UpdateODS(TOP_LEVELS_ODS, {"Stats", "Level"})
UpdateODS(WAVES_SURVIVED_ODS, {"Stats", "WavesSurvived"})
UpdateODS(DONATED_ROBUX_ODS, {"Stats", "DonatedRobux"})
UpdateODS(TOP_KILLS_ODS, {"Stats", "TotalKills"})
UpdateCache(TOP_LEVELS_ODS, caches.global, "level", 10)
UpdateCache(WAVES_SURVIVED_ODS, caches.global, "wavesSurvived", 10)
UpdateCache(DONATED_ROBUX_ODS, caches.global, "donatedRobux", 10)
UpdateCache(TOP_KILLS_ODS, caches.global, "kills", 10)
--visual
--print("[GLOBAL]")
--PrintTopPlayers(caches.global)
for topType, tops in pairs(caches.global) do
--print(string.upper(topType))
if LOBBY.BulletinBoard.SurfaceGuis.Global.Slides:FindFirstChild(topType) then
--get slide
local slide = LOBBY.BulletinBoard.SurfaceGuis.Global.Slides[topType]
--reset image
for _, image in pairs(slide.Places:GetChildren()) do
image.ProfileImage.Image = ""
end
for _, item in pairs(slide.List:GetChildren()) do
item:Destroy()
end
local y = 0
for place, playerInfo in pairs(tops) do
if slide.Places:FindFirstChild(place) then
slide.Places[place].ProfileImage.Image = playerInfo.image
end
local item = GLOBAL_LIST_ITEM:clone()
item.Place.Text = place .. FormatPlace(place)
item.User.Text = playerInfo.name
item.Type.Text = AbbreviateNumber(playerInfo[topType])
item.Position = UDim2.new(0, 0, 0, y)
item.Parent = slide.List
y = y + 70
--print(place, playerInfo.name, AbbreviateNumber(playerInfo[topType]))
end
--adjust scroll
slide.List.CanvasSize = UDim2.new(0, 0, 0, 265 + math.max(1, y - 265))
end
end
end
function LeaderBoardService.CheckTopPlace(ODSName, player, pageSize)
local ODS = game:GetService("DataStoreService"):GetOrderedDataStore(ODSName)
local success, pages = pcall(function()
return ODS:GetSortedAsync(false, pageSize)
end)
if success and pages then
local currentPage = pages:GetCurrentPage()
for place, item in pairs(currentPage) do
local userID = item.key
if item.value > 0 and tostring(player.UserId) == tostring(userID) then
return true, place, item.value
end
end
return true, nil, nil
else
player:Kick("Error loading data, please rejoin")
end
end
--regular methods
function LeaderBoardService:UpdateWeekly()
UpdateODS(WEEKLY_TOP_KILLS_ODS, {"WeeklyStats", "Kills"})
UpdateCache(WEEKLY_TOP_KILLS_ODS, caches.weekly, "kills", MAX_WEEKLY_PLACES)
--reset image
for _, image in pairs(LOBBY.BulletinBoard.SurfaceGuis.Weekly.Places:GetChildren()) do
image.ProfileImage.Image = ""
end
for _, item in pairs(LOBBY.BulletinBoard.SurfaceGuis.Weekly.Main.List:GetChildren()) do
item:Destroy()
end
local y = 0
for place, playerInfo in pairs(caches.weekly.kills) do
if LOBBY.BulletinBoard.SurfaceGuis.Weekly.Places:FindFirstChild(place) then
LOBBY.BulletinBoard.SurfaceGuis.Weekly.Places[place].ProfileImage.Image = playerInfo.image
end
local item = WEEKLY_LIST_ITEM:clone()
item.Place.Text = place
item.User.Text = playerInfo.name
item.Type.Text = AbbreviateNumber(playerInfo.kills)
item.PrizeValue.Text = AbbreviateNumber(self.GetWeeklyPlacePrize(place))
item.Position = UDim2.new(0, 190, 0, y)
item.Parent = LOBBY.BulletinBoard.SurfaceGuis.Weekly.Main.List
y = y + 60
end
LOBBY.BulletinBoard.SurfaceGuis.Weekly.Main.List.CanvasSize = UDim2.new(0, 0, 0, 375 + math.max(1, y - 375))
--check for those in game
for _, player in pairs(PLAYERS:GetPlayers()) do
local playerData = DATA_MODULE.GetPlayerData(player)
if playerData and player:FindFirstChild("TempStats") and player.TempStats.Loaded.Value == true then
local playerPlace, playerPrize, playerKills = nil, nil, playerData.WeeklyStats.Kills
for place, playerInfo in pairs(caches.weekly.kills) do
if tostring(player.UserId) == tostring(playerInfo.userID) then
playerPlace, playerPrize = place, self.GetWeeklyPlacePrize(place)
end
end
RE:FireClient(player, "UpdateWeeklyStats", {Place = playerPlace, Prize = playerPrize, Kills = playerKills})
end
end
end
function LeaderBoardService:ManageWeek()
local uCurrentWeek = self.GetCurrentWeek()
if not currentWeek or currentWeek ~= uCurrentWeek then
--update week once more
--UpdateWeekly()
print("CURRENT WEEK - " .. uCurrentWeek)
--award those in game while week changed
local success, pages = pcall(function()
return WEEKLY_TOP_KILLS_ODS:GetSortedAsync(false, MAX_WEEKLY_PLACES)
end)
if success and pages then
local currentPage = pages:GetCurrentPage()
for place, item in pairs(currentPage) do
local userID = item.key
for _, player in pairs(PLAYERS:GetPlayers()) do
local playerData = DATA_MODULE.GetPlayerData(player)
if playerData and player:FindFirstChild("TempStats") and player.TempStats.Loaded.Value == true then
if item.value > 0 and tostring(player.UserId) == tostring(userID) then
local prize = self.GetWeeklyPlacePrize(place)
DATA_MODULE.EditData(player, {
{Type = "AddCredits", Value = prize}
})
RE:FireClient(player, "WeeklyWin", {Week = currentWeek, Place = place, Prize = tostring(prize)})
end
end
end
end
end
--set new ods
WEEKLY_TOP_KILLS_ODS = game:GetService("DataStoreService"):GetOrderedDataStore("Week" .. uCurrentWeek .. "TopKills")
--clear cache
caches.weekly.kills = {}
--set last week
currentWeek = uCurrentWeek
end
--clear weekly player data, if oudated week
--this happens its own code when the player rejoins anyways
for _, player in pairs(PLAYERS:GetPlayers()) do
local plyrData = DATA_MODULE.GetPlayerData(player)
if player:FindFirstChild("TempStats") and player.TempStats:FindFirstChild("Loaded") and player.TempStats.Loaded.Value == true and plyrData and plyrData.WeeklyStats.Week ~= uCurrentWeek then
DATA_MODULE.EditData(player, {
{Type = "ResetWeeklyStats", Week = uCurrentWeek},
})
end
end
--
local nextWeekEndTime = SECONDS_IN_WEEK + ((currentWeek * SECONDS_IN_WEEK) + WEEK_ONE_START_TIME)
LOBBY.BulletinBoard.SurfaceGuis.Weekly.Info.Time.Time.Text = FormatCountdownDHMS(math.max(0, nextWeekEndTime - os.time()))
end
function LeaderBoardService:Update()
self:ManageWeek()
--update last updated
if not self.updating and ((tick() - lastUpdated) > UPDATE_CYCLE) then
delay(0, function()
--if needed, then false
self.updating = true
self.UpdateGlobal()
self:UpdateWeekly()
lastUpdated = tick()
self.updating = false
end)
end
local seconds = math.floor(tick() - lastUpdated)
LOBBY.BulletinBoard.SurfaceGuis.Update.Time.Text = self.updating == true and "UPDATING" or string.format("%02d min %02d sec",(seconds/60)%60, seconds%60)
end
--start it off
function LeaderBoardService:Start()
print("LeaderBoardService started...")
self:ManageWeek()
end
return LeaderBoardService