· 8 years ago · Mar 12, 2018, 11:06 PM
1local module = {} -- Handles the game's datastores
2
3local DiscordModule = require(script.Parent:WaitForChild("DiscordPost"));
4
5
6local Scope = "Release" -- The scope of our data store
7
8
9local DataStoreService = game:GetService("DataStoreService");
10local HttpService = game:GetService("HttpService");
11
12
13local PlayerDataStore; -- The player data store that will be retrieved from at the beginning of the game, and updated when needed
14local OldPlayerDataStore;
15local TopKillsStore;
16local TopHeadshotsStore;
17local TopDeathsStore;
18
19local PlayerDataStoreCache = {}; -- The table we'll be updating to throughout the game
20
21local Players = game:GetService("Players");
22
23
24local Communication = game:WaitForChild("ReplicatedStorage"):WaitForChild("Communication");
25local RemoteEvent = Communication:WaitForChild("RemoteEvent");
26
27
28
29
30repeat
31 local success,message = pcall(function()
32 PlayerDataStore = DataStoreService:GetDataStore("PlayerDataV2", Scope); -- Retrieve player data datastore with set scope
33 OldPlayerDataStore = DataStoreService:GetDataStore("PlayerData", Scope);
34 TopKillsStore = DataStoreService:GetOrderedDataStore("TopKills",Scope);
35 TopHeadshotsStore = DataStoreService:GetOrderedDataStore("TopHeadshots",Scope);
36 TopDeathsStore = DataStoreService:GetOrderedDataStore("TopDeathsStore",Scope);
37 end)
38 if not success then
39 print("Error retrieving PlayerDataStore: "..message);
40 end
41wait();
42until PlayerDataStore and TopKillsStore and TopHeadshotsStore and TopDeathsStore and OldPlayerDataStore;
43
44
45function module.AddKill(client,headshot)
46 local success,err = pcall(function()
47 TopKillsStore:IncrementAsync(client.UserId,1);
48
49 if headshot then
50 TopHeadshotsStore:IncrementAsync(client.UserId,1);
51 end
52 end)
53end
54
55function module.AddDeath(client)
56 local success,err = pcall(function()
57 TopDeathsStore:IncrementAsync(client.UserId,1);
58 end)
59end
60
61
62
63local Leaderboards = Workspace:WaitForChild("Leaderboards");
64local TopKillsBoard = Leaderboards:WaitForChild("TopKills");
65local TopHeadshotsBoard = Leaderboards:WaitForChild("TopHeadshots");
66local TopDeathsBoard = Leaderboards:WaitForChild("TopDeaths");
67
68local TopKillsTemplate = script:WaitForChild("TopKillsStats");
69local UsernameCache = {}
70
71local function GetNameFromUserId(userId)
72
73 local cacheItem = UsernameCache[userId]
74 if not cacheItem then
75 local be = Instance.new("BindableEvent")
76 cacheItem = {
77 Event = be.Event,
78 Finished = false,
79 Result = nil
80 }
81
82 UsernameCache[userId] = cacheItem
83 coroutine.wrap(function()
84 pcall(function()
85 local userName = game:GetService("Players"):GetNameFromUserIdAsync(userId)
86 cacheItem.Result = userName
87 cacheItem.Finished = true
88 be:Fire()
89 be:Destroy()
90 cacheItem.Event = nil
91 end)
92 end)()
93 end
94
95 if not cacheItem.Finished then cacheItem.Event:Wait() end
96 return cacheItem.Result
97end
98
99local function UpdateBoard(board,data)
100 local surfacegui = board:WaitForChild("BoardPart"):WaitForChild("SurfaceGui");
101 local frame = surfacegui:WaitForChild("Frame"):WaitForChild("RoundedFrame"):WaitForChild("ScrollingFrame");
102
103 for _,v in pairs(frame:GetChildren()) do
104 if v:isA("Frame") then
105 v:Destroy();
106 end
107 end
108
109 for rank,v in pairs(data) do
110 local userid = v.key;
111 local kills = v.value;
112
113 coroutine.wrap(function()
114 local name = GetNameFromUserId(userid)
115 local template = TopKillsTemplate:clone();
116 local killsframe = template:WaitForChild("Kills");
117 local playerframe = template:WaitForChild("Player");
118 local rankframe = template:WaitForChild("Rank");
119
120 rankframe.TextLabel.Text = "#"..rank;
121 playerframe.Frame.RoundedFrame.TextLabel.Text = name;
122 killsframe.TextLabel.Text = kills;
123
124 if name ~= "" then
125 template.LayoutOrder=rank;
126 template.Parent=frame;
127 end
128
129 local icon, ready
130 while not ready do
131 icon, ready = Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size100x100);
132 if not ready then wait() end
133 end
134
135 playerframe.PlayerIcon.Image = icon
136 end)();
137 end
138end
139
140function module.UpdateLeaderboards()
141
142 print("Updating leaderboards");
143
144 pcall(function()
145 local ordered_topkills = TopKillsStore:GetSortedAsync(false,10):GetCurrentPage();
146 local ordered_topdeaths = TopDeathsStore:GetSortedAsync(false,10):GetCurrentPage();
147 local ordered_topheadshots = TopHeadshotsStore:GetSortedAsync(false,10):GetCurrentPage();
148 UpdateBoard(TopKillsBoard,ordered_topkills);
149 UpdateBoard(TopHeadshotsBoard,ordered_topheadshots);
150 UpdateBoard(TopDeathsBoard,ordered_topdeaths);
151 end)
152end
153
154
155
156
157local OngoingUpdates = {}
158
159local function UpdatePlayerDataToStore(client, newdata) -- void
160 print("UPDATING DATASTORE");
161 local success, output
162
163 local now = tick()
164 OngoingUpdates[client] = now
165
166 while not success and OngoingUpdates[client] == now do
167 success, output = pcall(PlayerDataStore.SetAsync, PlayerDataStore, client.UserId, newdata)
168
169 if not success then
170 coroutine.wrap(function()
171 DiscordModule.Post(client, "Error", output, debug.traceback());
172 end)();
173 wait(2)
174 end
175 end
176
177 if OngoingUpdates[client] == now then
178 OngoingUpdates[client] = nil
179 end
180end
181
182
183
184
185
186local dataTemplate =
187{
188 OwnedItems={},
189 Gold=0,
190 UsedCodes={},
191 PurchasedItemVersions={},
192 NumLoadouts=0,
193 Loadouts={},
194 OwnedSkins={}
195 }
196
197local IsStudio = game:GetService("RunService"):IsStudio()
198
199
200local function SetupPlayerDataStoreCache(client)
201 local dataLoaded = Instance.new("BindableEvent")
202 local cacheEntry = {
203 IsFinished = false,
204 OnFinish = dataLoaded.Event,
205 Result = nil
206 }
207
208 PlayerDataStoreCache[client] = cacheEntry
209
210 coroutine.wrap(function()
211 local targetDataStore = PlayerDataStore
212
213 while client:IsDescendantOf(Players) do
214 local success, output = pcall(targetDataStore.GetAsync, targetDataStore, client.UserId)
215
216 if not success and IsStudio and output:find("API Services rejected") then
217 success = true
218 output = nil
219 end
220
221 if success then
222 if not output then -- No data found
223 if targetDataStore == PlayerDataStore then -- If there was nothing in the new data store, try old datastore
224 targetDataStore = OldPlayerDataStore
225 else -- if there was nothing in the old data store, create new data
226 cacheEntry.Result = {
227 OwnedItems={},
228 Gold=0,
229 UsedCodes={},
230 PurchasedItemVersions={},
231 NumLoadouts=0,
232 Loadouts={},
233 OwnedSkins={}
234 };
235 break
236 end
237
238 else -- Data found
239
240 -- Deep copy missing stuff from dataTemplate
241 local function fillTable(tab, template)
242 for i,v in pairs(template) do
243 if not tab[i] then
244 tab[i] = typeof(v) == "table" and fillTable({}, v) or v
245 end
246 end
247
248 return tab
249 end
250
251 fillTable(output, dataTemplate)
252 cacheEntry.Result = output;
253 break
254 end
255 else -- If there is a datastore error, wait and repeat
256 print("Failed to load data for " .. client.UserId, output)
257 wait(1);
258 end
259 end
260
261 if not client:IsDescendantOf(Players) then
262 cacheEntry.Result = nil
263 end
264
265 cacheEntry.IsFinished = true
266 dataLoaded:Fire()
267 end)()
268
269 return cacheEntry
270end
271
272local function GetPlayerData(client)
273 local cacheEntry = PlayerDataStoreCache[client];
274 if not cacheEntry then
275 if client:IsDescendantOf(Players) then
276 cacheEntry = SetupPlayerDataStoreCache(client)
277 else
278 return nil
279 end
280 end
281
282 -- Wait for data to load or player to leave
283 if not cacheEntry.IsFinished then cacheEntry.OnFinish:Wait() end
284
285 return cacheEntry.Result
286end
287
288
289local IsRequestingPlayerDataUpdate = {}
290local function RequestUpdatePlayerData(client)
291 local playerdata = GetPlayerData(client)
292 if not playerdata then return end
293
294 if not IsRequestingPlayerDataUpdate[client] then
295 IsRequestingPlayerDataUpdate[client] = true
296 spawn(function()
297 IsRequestingPlayerDataUpdate[client] = nil
298 UpdatePlayerDataToStore(client, playerdata)
299 end)
300 end
301end
302
303
304game:BindToClose(function()
305 for _,v in pairs(Players:GetPlayers()) do
306 RequestUpdatePlayerData(v);
307 end
308end)
309
310
311local function PlayerRemoving(client)
312 RequestUpdatePlayerData(client)
313
314 PlayerDataStoreCache[client] = nil
315end
316
317Players.PlayerRemoving:connect(PlayerRemoving);
318
319
320
321module.SaveLoadouts = function(client,loadouts,numloadouts)
322 local playerdata = GetPlayerData(client);
323 playerdata.Loadouts=loadouts;
324 playerdata.NumLoadouts=numloadouts;
325 RequestUpdatePlayerData(client);
326end
327module.GetLoadouts = function(client)
328 local playerdata = GetPlayerData(client);
329 if playerdata then
330 return playerdata.Loadouts;
331 end
332end
333
334
335function module.GetPlayerData(client)
336 return GetPlayerData(client)
337end
338
339function module.UpdatePlayerData(client)
340 return RequestUpdatePlayerData(client)
341end
342
343
344-- Codes
345function module.HasUsedCode(client, code)
346 local playerdata = GetPlayerData(client)
347 if not playerdata then return end
348
349 return not not playerdata.UsedCodes[code]
350end
351
352function module.UseCode(client, code)
353 local playerdata = GetPlayerData(client)
354 if not playerdata then return end
355
356 playerdata.UsedCodes[code] = true;
357 RequestUpdatePlayerData(client)
358end
359
360
361-- Gold
362function module.GetGold(client)
363 local playerdata = GetPlayerData(client)
364 if not playerdata then return end
365
366 return playerdata.Gold
367end
368
369function module.GiveSkin(client,tab)
370 local playerdata = GetPlayerData(client);
371 if not playerdata then return end
372
373 local skintype = tab.skintype;
374
375 local skintypes = playerdata.OwnedSkins[skintype];
376 if not skintypes then
377 skintypes={};
378 playerdata.OwnedSkins[skintype]=skintypes;
379 end
380
381 if not skintypes[tab.skin] then -- If they don't already have this skin
382 skintypes.Equipped=tab.skin;
383 skintypes[tab.skin]={AlreadyUnlocked=false};
384
385 print("Gave skin to ",client);
386
387 else
388
389 skintypes[tab.skin]={AlreadyUnlocked=true};
390 end
391
392 playerdata.OwnedSkins[skintype]=skintypes;
393
394
395 RequestUpdatePlayerData(client);
396
397
398end
399
400
401
402function module.GiveGold(client, amount)
403 local playerdata = GetPlayerData(client)
404 if not playerdata then return end
405
406 playerdata.Gold = playerdata.Gold + amount
407 RequestUpdatePlayerData(client)
408end
409
410function module.SpendGold(client, amount)
411 local playerdata = GetPlayerData(client)
412 if not playerdata then return end
413
414 playerdata.Gold = playerdata.Gold - amount
415 RequestUpdatePlayerData(client)
416end
417
418
419function module.TriggerFirstGoldPurchase(client)
420 local playerdata = GetPlayerData(client)
421 if not playerdata then return end
422
423 if not playerdata.BoughtGoldBefore then
424 playerdata.BoughtGoldBefore = true
425 RequestUpdatePlayerData(client)
426 return true
427 end
428
429 return false
430end
431
432
433-- Items
434function module.GiveItem(client, itemname)
435 local playerdata = GetPlayerData(client)
436 if not playerdata then return end
437
438 playerdata.OwnedItems[itemname] = true
439 RequestUpdatePlayerData(client)
440end
441
442function module.HasItem(client, itemname)
443 local playerdata = GetPlayerData(client)
444 if not playerdata then return end
445
446 return not not playerdata.OwnedItems[itemname]
447end
448
449function module.GetOwnedItems(client)
450 local playerdata = GetPlayerData(client)
451 if not playerdata then return end
452
453 return playerdata.OwnedItems
454end
455
456
457
458
459-- System to determine if players purchased an item before its most recent update
460-- If they purchased the item before its most recent update, compensate them accordingly
461
462
463local ItemUpdates = -- Item updates that should compensate players
464{
465 ["Blaster"]
466 =
467 {
468 Version=2,
469 Compensation={Gold=5000}
470 }
471}
472
473local function Compensate(client,updates)
474
475
476 local playerdata = GetPlayerData(client)
477
478
479 if not playerdata then return end
480
481
482 if not updates then return end
483
484 local compensation = updates.Compensation;
485 if compensation then
486
487 if compensation.Gold then
488 module.GiveGold(client,compensation.Gold);
489 RemoteEvent:FireClient(client,client, "UpdatePlayerData")
490 end
491 end
492end
493
494function module.HandleUpdatedItems(client)
495
496 local playerdata = GetPlayerData(client)
497 if not playerdata then return end
498
499 local owneditems = playerdata.OwnedItems;
500 if owneditems then
501
502 for name,bool in pairs(owneditems) do
503 local updates = ItemUpdates[name]; -- Get item update list for this item
504 if updates then -- If update list exists
505
506 local purchaseditemversions = playerdata.PurchasedItemVersions; -- Get player purchased item versions storage
507 if purchaseditemversions then -- If storage found
508
509 local purchaseditemversion = purchaseditemversions[name];
510
511 if not purchaseditemversion then
512 purchaseditemversion=0;
513 purchaseditemversions[name]=purchaseditemversion;
514 end
515
516 if purchaseditemversion then -- If purchased item data found for this item
517
518 if purchaseditemversion ~= updates.Version then -- If the found version isnt the current version, compensate
519 purchaseditemversions[name]=updates.Version;
520
521 print("Player compensated");
522 Compensate(client,updates);
523 end
524 end
525
526 RequestUpdatePlayerData(client);
527
528 end
529 end
530 end
531
532 end
533end
534
535
536
537
538return module