· 6 months ago · Mar 16, 2025, 03:25 AM
1local ProfileService = require(game.ServerScriptService.ProfileService)
2
3-----------------------------------------------------------
4-- DataStore Configuration
5-----------------------------------------------------------
6local DataStoreName = "PlayerData_1" -- Change this name to reset all data
7
8-- Default data template for new players.
9-- If a player joins for the first time, their data will be initialized using these values.
10local DataTemplate = {
11 Coins = 0, -- Integer: Total coins the player has
12 Loot = {}, -- Dictionary: key = loot name, value = quantity
13 Weapons = {"Bow"}, -- Array: List of weapon names the player owns (default: "Bow")
14 Kills = 0, -- Integer: Total number of kills
15 Equipped_Weapon = "Bow" -- String: The weapon currently equipped (default: "Bow")
16}
17
18-----------------------------------------------------------
19-- Global Variables
20-----------------------------------------------------------
21local Profiles = {} -- Table to store each player's loaded profile during gameplay
22
23-- Create the ProfileStore using ProfileService.
24-- This connects to the DataStore using the DataTemplate.
25local ProfileStore = ProfileService.GetProfileStore(DataStoreName, DataTemplate)
26
27-----------------------------------------------------------
28-- Remote Setup for Live Data Updates
29-----------------------------------------------------------
30local ReplicatedStorage = game:GetService("ReplicatedStorage")
31-- Get (or create) the Remotes folder in ReplicatedStorage.
32local Remotes = ReplicatedStorage:FindFirstChild("Remotes") or Instance.new("Folder")
33Remotes.Name = "Remotes"
34Remotes.Parent = ReplicatedStorage
35
36-- RemoteEvent to push live data updates to the client.
37local UpdateEvent = Remotes:FindFirstChild("UpdatePlayerData") or Instance.new("RemoteEvent")
38UpdateEvent.Name = "UpdatePlayerData"
39UpdateEvent.Parent = Remotes
40
41-- RemoteFunction to allow clients to fetch their data on startup.
42local GetDataFunction = Remotes:FindFirstChild("GetPlayerData") or Instance.new("RemoteFunction")
43GetDataFunction.Name = "GetPlayerData"
44GetDataFunction.Parent = Remotes
45
46-----------------------------------------------------------
47-- DataManager Table
48-----------------------------------------------------------
49local DataManager = {}
50
51-----------------------------------------------------------
52-- Function: DataManager.SendUpdate
53-- Description: Sends the current profile data to the specified player's client.
54-- Parameters: player (Player) – the player whose data to update.
55-----------------------------------------------------------
56function DataManager.SendUpdate(player)
57 local profile = Profiles[player]
58 if profile then
59 -- Fire the remote event with the player's current data table.
60 UpdateEvent:FireClient(player, profile.Data)
61 end
62end
63
64-----------------------------------------------------------
65-- Function: DataManager.LoadData
66-- Description: Loads a player's data using ProfileService when they join.
67-- If the player has no previous data, it initializes with DataTemplate.
68-- It also sets up a listener to kick the player if the profile is released.
69-- Finally, if the player is still in-game, it stores their profile and sends an initial update.
70-----------------------------------------------------------
71function DataManager.LoadData(player)
72 -- Attempt to load the player's profile from the DataStore using their UserId.
73 local profile = ProfileStore:LoadProfileAsync("Player_" .. player.UserId)
74
75 if profile then
76 -- Link the profile to the player's UserId.
77 profile:AddUserId(player.UserId)
78 -- Reconcile the profile so any missing fields are filled using DataTemplate.
79 profile:Reconcile()
80 -- Listen for profile release (e.g. data error) and kick the player if it happens.
81 profile:ListenToRelease(function()
82 Profiles[player] = nil
83 player:Kick("Data Error: Please rejoin!")
84 end)
85
86 -- Ensure every field defined in DataTemplate exists in profile.Data.
87 for key, defaultValue in pairs(DataTemplate) do
88 if profile.Data[key] == nil then
89 profile.Data[key] = defaultValue
90 end
91 end
92
93 --print("Data loaded for", player.Name, profile.Data)
94
95 -- Only store the profile if the player is still in the game.
96 if player:IsDescendantOf(game.Players) then
97 Profiles[player] = profile
98 -- Send the initial data update to the player's client.
99 DataManager.SendUpdate(player)
100
101 -- Immediately equip the weapon if the player's character already exists.
102 if player.Character then
103 -- Wait a short moment to ensure the Backpack is ready.
104 local backpack = player:FindFirstChild("Backpack") or player:WaitForChild("Backpack")
105 task.wait(0.5)
106 DataManager.EquipWeapon(player, profile.Data.Equipped_Weapon)
107 end
108
109 -- Also listen for CharacterAdded to equip weapon on respawn.
110 player.CharacterAdded:Connect(function(character)
111 local backpack = player:FindFirstChild("Backpack") or player:WaitForChild("Backpack")
112 task.wait(0.5) -- Small delay for smooth loading
113 DataManager.EquipWeapon(player, profile.Data.Equipped_Weapon)
114 end)
115 else
116 profile:Release()
117 end
118 else
119 -- If profile fails to load, kick the player.
120 player:Kick("Data failed to load. Try rejoining.")
121 end
122end
123
124-----------------------------------------------------------
125-- Function: DataManager.EquipWeapon
126-- Description: Equips a weapon for the player by cloning the weapon tool from ReplicatedStorage.
127-- It removes any existing weapon from the player's Backpack, then equips the new weapon.
128-- Also updates the player's profile with the newly equipped weapon and sends a UI update.
129-- Parameters:
130-- player (Player): The player to equip the weapon.
131-- weaponName (string): The name of the weapon to equip.
132-----------------------------------------------------------
133function DataManager.EquipWeapon(player, weaponName)
134 -- Get the Weapons folder from ReplicatedStorage.
135 local Weapons = ReplicatedStorage:FindFirstChild("Weapons")
136 local profile = Profiles[player]
137 if not Weapons then
138 warn("Weapons folder missing in ReplicatedStorage!")
139 return
140 end
141
142 -- Find the weapon tool by name.
143 local clonedWeapon = Weapons:FindFirstChild(weaponName)
144 -- Wait for the player's Backpack.
145 local Backpack = player:WaitForChild("Backpack")
146 local StarterGear = player:WaitForChild("StarterGear")
147 if clonedWeapon and Backpack then
148 -- Remove any existing weapon tools from the Backpack.
149 for _, tool in ipairs(Backpack:GetChildren()) do
150 if tool:IsA("Tool") then
151 tool:Destroy()
152 end
153 end
154
155 -- Also clear StarterGear to ensure consistency.
156 for _, tool in ipairs(StarterGear:GetChildren()) do
157 if tool:IsA("Tool") then
158 tool:Destroy()
159 end
160 end
161
162 -- Clone the weapon tool and parent it to the Backpack.
163 local newWeapon = clonedWeapon:Clone()
164 newWeapon.Parent = Backpack
165
166 -- Clone for StarterGear as well.
167 local starterGearWeapon = clonedWeapon:Clone()
168 starterGearWeapon.Parent = StarterGear
169
170 -- Update the player's profile with the newly equipped weapon.
171 profile.Data.Equipped_Weapon = weaponName
172
173 -- Send a live update to the client.
174 DataManager.SendUpdate(player)
175 -- Debug (optional): print("Equipped", weaponName, "to", player.Name)
176 else
177 warn("Could not equip weapon for", player.Name)
178 end
179end
180
181-----------------------------------------------------------
182-- RemoteFunction: GetDataFunction.OnServerInvoke
183-- Description: When the client requests their data, this function waits until the player's profile is loaded
184-- (with a maximum wait time) and then returns the stored data. If data isn’t available, it returns the default template.
185-----------------------------------------------------------
186function GetDataFunction.OnServerInvoke(player)
187 local totalWait = 0
188 while not Profiles[player] and totalWait < 5 do
189 task.wait(0.2)
190 totalWait = totalWait + 0.2
191 end
192 if not Profiles[player] then
193 return DataTemplate
194 end
195 return Profiles[player].Data
196end
197
198-----------------------------------------------------------
199-- Function: DataManager.SaveData
200-- Description: Called when a player leaves. It releases their profile, which saves the data.
201-----------------------------------------------------------
202function DataManager.SaveData(player)
203 local profile = Profiles[player]
204 if profile then
205 profile:Release()
206 end
207end
208
209-----------------------------------------------------------
210-- Update Methods for Player Data
211-----------------------------------------------------------
212
213-- Function: DataManager.UpdateStat
214-- Updates a specific stat in the player's data, then sends a live update.
215function DataManager.UpdateStat(player, stat, value)
216 local profile = Profiles[player]
217 if profile and profile.Data[stat] ~= nil then
218 profile.Data[stat] = value
219 DataManager.SendUpdate(player)
220 end
221end
222
223-- Function: DataManager.AddLoot
224-- Adds a specified amount of loot to the player's data if it is obtainable,
225-- updates the player's profile, sends a live update, and triggers a GUI popup.
226function DataManager.AddLoot(player, lootName, amount)
227 local obtainableLoot = require(game.ServerScriptService.LootManager).GetObtainableLoot()
228 local profile = Profiles[player]
229 -- Only add loot if it's marked as obtainable.
230 if profile and obtainableLoot[lootName] then
231 profile.Data.Loot[lootName] = (profile.Data.Loot[lootName] or 0) + amount
232 DataManager.SendUpdate(player)
233
234 -- Trigger the loot popup GUI on the client.
235 local ReplicatedStorage = game:GetService("ReplicatedStorage")
236 local Remotes = ReplicatedStorage:WaitForChild("Remotes")
237 local LootPopupEvent = Remotes:FindFirstChild("LootPopupEvent")
238 if LootPopupEvent then
239 LootPopupEvent:FireClient(player, lootName, amount)
240 end
241 end
242end
243
244-- Function: DataManager.RemoveLoot
245-- Removes a specified amount of loot from the player's data if they have enough.
246-- Returns true if successful; false otherwise.
247function DataManager.RemoveLoot(player, lootName, amount)
248 local profile = Profiles[player]
249 if profile and profile.Data.Loot[lootName] and profile.Data.Loot[lootName] >= amount then
250 profile.Data.Loot[lootName] = profile.Data.Loot[lootName] - amount
251 if profile.Data.Loot[lootName] <= 0 then
252 profile.Data.Loot[lootName] = nil
253 end
254 DataManager.SendUpdate(player)
255 return true
256 end
257 return false
258end
259
260-- Function: DataManager.AddWeapon
261-- Adds a weapon to the player's owned list (if they don't already own it) and sends an update.
262function DataManager.AddWeapon(player, weaponName)
263 local profile = Profiles[player]
264 if profile and not table.find(profile.Data.Weapons, weaponName) then
265 table.insert(profile.Data.Weapons, weaponName)
266 DataManager.SendUpdate(player)
267 end
268end
269
270-- Function: DataManager.AddCoins
271-- Increments the player's coins by the given amount and sends an update.
272function DataManager.AddCoins(player, amount)
273 local profile = Profiles[player]
274 if profile then
275 profile.Data.Coins = profile.Data.Coins + amount
276 DataManager.SendUpdate(player)
277 end
278end
279
280-- Function: DataManager.RemoveCoins
281-- Deducts coins from the player's data if they have enough.
282-- Returns true if successful; false otherwise.
283function DataManager.RemoveCoins(player, amount)
284 local profile = Profiles[player]
285 if profile and profile.Data.Coins >= amount then
286 profile.Data.Coins = profile.Data.Coins - amount
287 DataManager.SendUpdate(player)
288 return true
289 end
290 return false
291end
292
293-- Function: DataManager.AddKill
294-- Increments the player's kill count by one and sends an update.
295function DataManager.AddKill(player)
296 local profile = Profiles[player]
297 if profile then
298 profile.Data.Kills = profile.Data.Kills + 1
299 DataManager.SendUpdate(player)
300 end
301end
302
303-----------------------------------------------------------
304-- Connect Player Events
305-----------------------------------------------------------
306game.Players.PlayerAdded:Connect(DataManager.LoadData) -- Load data when a player joins
307game.Players.PlayerRemoving:Connect(DataManager.SaveData) -- Save data when a player leaves
308
309-----------------------------------------------------------
310-- Return the DataManager module
311-----------------------------------------------------------
312return DataManager
313