Advertisement
Toxicator1

PlayerStatManager

Jun 8th, 2020
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.06 KB | None | 0 0
  1. --[[
  2.  
  3. This system does not cover edge cases:
  4. - Player leaves and rejoins quickly
  5. - Player joins, data doesn't fully load, game crashes (bindToClose fires)
  6.  
  7. API:
  8. - setup()
  9. - savePlayerData(player)
  10. - *STUDIO ONLY* wipePlayerData(player)
  11. - bindToPlayerDataLoaded(func)
  12.  
  13. - changeTokens(player)
  14. - addCharacter(player)
  15. - addDoor(player)
  16.  
  17. - getTokens(player)
  18. - getDoors(player)
  19. - getCharacters(player)
  20.  
  21. - loadedDataOk(player)
  22. - hasDataLoaded(player)
  23. - hasDoor(player)
  24. - hasCharacter(player)
  25. --]]
  26.  
  27. local PlayerStatManager = {}
  28.  
  29. local Players = game:GetService("Players")
  30. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  31. local DataStoreService = game:GetService("DataStoreService")
  32. local PlayerData = DataStoreService:GetDataStore("PlayerData")
  33. local cRequire = require(ReplicatedStorage:WaitForChild("CustomRequire"))
  34.  
  35. local RemotesHandler = cRequire("RemotesHandler")
  36. local CombatCharacterService = cRequire("CombatCharacterService")
  37. local DoorService = cRequire("DoorService")
  38. local isPlayer = cRequire("IsPlayer")
  39.  
  40. local sessionData = {}
  41. local loadedDataOk = {}
  42. local onPlayerDataLoaded
  43.  
  44. local DATA_VERSION = 1
  45. local STARTER_TOKENS = 0
  46. local MINIMUM_TOKENS = 0
  47. local getStarterData = function ()
  48. return
  49. {
  50. ["Tokens"] = STARTER_TOKENS,
  51. ["Characters"] = {["001"]=true, ["002"]=true, ["003"]=true, ["004"]=true, ["005"]=true},
  52. ["Doors"] = {},
  53. ["DataVersion"] = DATA_VERSION
  54. }
  55. end
  56.  
  57. -- NOTE: for players like tommy/andy who have old data,
  58. -- this won't work - find a fix to this -> edit data stores
  59.  
  60. function PlayerStatManager.setup()
  61. local function setupPlayerData(player)
  62. sessionData[player.UserId] = {}
  63.  
  64. -- PlayerData
  65. local success, data = pcall(function ()
  66. return(PlayerData:GetAsync(tostring(player.UserId)))
  67. end)
  68.  
  69. if success then
  70. if data then
  71. -- Check and replace outdated data versions
  72. if data.DataVersion and data.DataVersion == DATA_VERSION then
  73. sessionData[player.UserId] = data
  74. else
  75. print'wrong data version loaded, setting new'
  76. sessionData[player.UserId] = getStarterData()
  77. end
  78. else
  79. -- First time playing - give them default data
  80. sessionData[player.UserId] = getStarterData()
  81. end
  82. loadedDataOk[player.UserId] = true
  83. if onPlayerDataLoaded then
  84. onPlayerDataLoaded(player, sessionData[player.UserId])
  85. end
  86. else
  87. -- Player's data failed to load
  88. warn(player.Name.."s data failed to load! Data will not save for this player.")
  89. sessionData[player.UserId] = getStarterData()
  90. end
  91.  
  92. if player.Parent then
  93. -- Code below runs when player leaves
  94. player:GetPropertyChangedSignal("Parent"):Connect(function ()
  95. if player.Parent == nil then
  96. PlayerStatManager.savePlayerData(player)
  97. sessionData[player.UserId] = nil
  98. if loadedDataOk[player.UserId] then
  99. loadedDataOk[player.UserId] = nil
  100. end
  101. end
  102. end)
  103. else
  104. -- Covers edge case: Player leaves before leave connection added
  105. sessionData[player.UserId] = nil
  106. end
  107. end
  108.  
  109. local function saveAllPlayerData()
  110. for _, player in pairs(Players:GetPlayers()) do
  111. PlayerStatManager.savePlayerData(player)
  112. end
  113. end
  114.  
  115. for _, player in pairs(Players:GetPlayers()) do
  116. setupPlayerData(player)
  117. end
  118. Players.PlayerAdded:Connect(setupPlayerData)
  119. -- Todo: check if this bind to close is safe
  120. game:BindToClose(saveAllPlayerData)
  121.  
  122. -- Requests
  123. local getDataRF = RemotesHandler.newRemoteFunction("OnPlayerDataRequest")
  124. getDataRF.OnServerInvoke = function (player)
  125. return sessionData[player.UserId]
  126. end
  127. end
  128.  
  129. function PlayerStatManager.savePlayerData(player)
  130. assert(isPlayer(player))
  131.  
  132. if not loadedDataOk[player.UserId] then
  133. warn(player.Name.."'s data didn't save. Player's data didn't load")
  134. return false
  135. end
  136.  
  137. PlayerData:SetAsync(tostring(player.UserId), sessionData[player.UserId])
  138. return true
  139. end
  140.  
  141. -- Studio only function for testing
  142. function PlayerStatManager.wipePlayerData(player)
  143. assert(isPlayer(player))
  144. assert(game:GetService("RunService"):IsStudio())
  145.  
  146. loadedDataOk[player.UserId] = nil
  147. PlayerData:RemoveAsync(player.UserId)
  148. print(player.Name.."'s data cleared")
  149. end
  150.  
  151. function PlayerStatManager.bindToPlayerDataLoaded(func)
  152. onPlayerDataLoaded = func
  153. end
  154.  
  155. ---------------------- MUTATORS ----------------------
  156.  
  157. function PlayerStatManager.changeTokens(player, changeValue)
  158. assert(isPlayer(player))
  159. assert(typeof(changeValue) == "number" and changeValue%1 == 0)
  160.  
  161. sessionData[player.UserId].Tokens = sessionData[player.UserId].Tokens + changeValue
  162.  
  163. -- Don't allow negative token balance
  164. if sessionData[player.UserId].Tokens < MINIMUM_TOKENS then
  165. sessionData[player.UserId].Tokens = MINIMUM_TOKENS
  166. end
  167. end
  168.  
  169. function PlayerStatManager.addCharacter(player, idOrName)
  170. assert(isPlayer(player))
  171. assert(CombatCharacterService.getCharacterFromIdOrName(idOrName))
  172.  
  173. local requestedCharacter = CombatCharacterService.getCharacterFromIdOrName(idOrName)
  174. if requestedCharacter.Wave == "DLC" then
  175. error("Attempt to save "..requestedCharacter.Name.." to "..player.Name.."'s owned characters")
  176. end
  177.  
  178. sessionData[player.UserId].Characters[requestedCharacter.Id] = true
  179. end
  180.  
  181. function PlayerStatManager.addDoor(player, doorId)
  182. assert(isPlayer(player))
  183. assert(DoorService.getDoor(doorId))
  184.  
  185. sessionData[player.UserId].Doors[doorId] = true
  186. end
  187.  
  188. ---------------------- ACCESSORS ----------------------
  189.  
  190. function PlayerStatManager.getTokens(player)
  191. assert(isPlayer(player))
  192.  
  193. return sessionData[player.UserId].Tokens
  194. end
  195.  
  196. function PlayerStatManager.getDoors(player)
  197. assert(isPlayer(player))
  198.  
  199. -- Create new table so that session data one cannot be modified
  200. local proxyTable = {}
  201. for k, v in pairs(sessionData[player.UserId].Doors) do
  202. proxyTable[k] = v
  203. end
  204.  
  205. return proxyTable
  206. end
  207.  
  208. -- Note: Does not include DLC characters
  209. function PlayerStatManager.getCharacters(player)
  210. assert(isPlayer(player))
  211.  
  212. -- Create new table so that session data one cannot be modified
  213. local characterTable = {}
  214. for k, v in pairs(sessionData[player.UserId].Characters) do
  215. characterTable[k] = v
  216. end
  217.  
  218. return characterTable
  219. end
  220.  
  221. function PlayerStatManager.loadedDataOk(player)
  222. assert(isPlayer(player))
  223. return loadedDataOk[player.UserId]
  224. end
  225.  
  226. function PlayerStatManager.hasDataLoaded(player)
  227. assert(isPlayer(player))
  228. return sessionData[player.UserId] and true or false
  229. end
  230.  
  231. function PlayerStatManager.hasDoor(player, doorId)
  232. assert(isPlayer(player))
  233. assert(typeof(doorId) == "string")
  234. assert(DoorService.getDoor(doorId))
  235.  
  236. return sessionData[player.UserId]["Doors"][doorId] or false
  237. end
  238.  
  239. function PlayerStatManager.hasCharacter(player, characterId)
  240. assert(isPlayer(player))
  241. assert(typeof(characterId) == "string")
  242.  
  243. local requestedCharacter = CombatCharacterService.getCharacterFromIdOrName(characterId)
  244. assert(requestedCharacter)
  245.  
  246. if requestedCharacter.Wave == "DLC" then
  247. print'checking if dlc owned...'
  248.  
  249. end
  250.  
  251. return sessionData[player.UserId]["Characters"][characterId] or false
  252. end
  253.  
  254. return PlayerStatManager
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement