Data storage System

I was really unhappy with my messy data storage system which had a lot of repeated code and adding new stats was overly complicated forcing you to add 4+ lines for every single stat. Idk why but I just feel like this could be improved. I am going to make it store versions of your data just in case. But anyway’s I tried making it a bit more performant by just removing some junk code but anyway’s here it is :smiley: Roast me hard for the code XD I coded this live in like 3 hours. Without sleep ;(

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStorage = game:GetService("DataStoreService")
local Datastore = DataStorage:GetDataStore("Game_Data_01")

local DataTable = {}

local HttpService = game:GetService("HttpService")
local WebHook = ""

local days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
local months = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}


local function LogMessage(Message, Player, ScriptName)
	if Player then
		Player = tostring(Player)
	else
		Player = "Server"
	end
	if not ScriptName then
		ScriptName = "Not Specified"
	end
	local Time = os.date("*t", os.time())
	if Time.sec < 10 then
		Time.sec = "0" .. Time.sec
	end
	if Time.min < 10 then
		Time.min = "0" .. Time.min
	end
	if Time.hour < 10 then
		Time.hour = "0" .. Time.hour
	end
	local Data = {
		["content"] = tostring("```ini" .. "\n" .. "[Sent by:] " .. Player .. "\n" .. "[Date:] " .. Time.year .. ", " .. months[Time.month] .. ", " .. days[Time.wday] .. " " .. Time.hour .. ":" .. Time.min .. ":" .. Time.sec ..  "\n" .. "[Script Location:] " .. ScriptName .. "\n" .. "[Message:] " .. tostring(Message) .. "```")
	}
	
	Data = HttpService:JSONEncode(Data)
	HttpService:PostAsync(WebHook, Data)
end

local function LoadDataIndexes(Player)
	
	local Stats = Player.Data
	
	local success, PlayerData = pcall(function() return Datastore:GetAsync(Player.UserId) end)
	if success then
		if PlayerData then
			for i,v in pairs(PlayerData) do
				if typeof(v[2]) == "table" then
	 				local Stat = Stats:FindFirstChild(v[1])
					if Stat then
						Stat.Value = Vector3.new(v[2][1], v[2][2], v[2][3])
					end
				else
	 				local Stat = Stats:FindFirstChild(v[1])
					if Stat then
						Stat.Value = v[2]
					end
				end
			end
		else
			return
		end
	else
		Player:Kick("Data failed to load!")
		LogMessage(PlayerData, Player.Name, script.Parent:GetFullName())
	end
end

local function SaveDataIndexes(Player)
	
	local Stats = Player.Data
	local DataFile = {}
	
	for i,v in pairs(DataTable[Player.Name]) do
		local Stat = Stats:FindFirstChild(v[1])
		if Stat then
			if Stat:IsA("Vector3Value") then
				table.insert(DataFile, {v[1], {Stat.Value.X, Stat.Value.Y, Stat.Value.Z}})
			else
				table.insert(DataFile, {v[1], Stat.Value})
			end
		end
	end
	Datastore:SetAsync(Player.UserId, DataFile)
	return
end

local function AddNonSaveDataIndex(Player, Name, TypeOfData, Value)
	local Stats = Player.Data
	local success = pcall(function()
		local IndexObject = Instance.new(TypeOfData, Stats)
		IndexObject.Name = Name
		IndexObject.Value = Value
		return
	end)
	if not success then
		print("Something's wrong there chief with the index : " .. Name .. "  | The TypeOf is " .. TypeOfData)
	end
end

local function AddDataIndex(Player, Name, TypeOfData, Value)
	local Stats = Player.Data
	local success = pcall(function()
		local IndexObject = Instance.new(TypeOfData, Stats)
		IndexObject.Name = Name
		IndexObject.Value = Value
		return
	end)
	if success then
		local Index = {Name, TypeOfData, Value}
		table.insert(DataTable[Player.Name], Index)
	else
		print("Something's wrong there chief with the index : " .. Name .. "  | The TypeOf is " .. TypeOfData)
	end
end

local function CreateDataFile(Player)
	
	local Stats = Instance.new("Folder", Player)
	Stats.Name = "Data"
	
	local Data = {}
	DataTable[Player.Name] = Data
	return(DataTable[Player.Name])
end

local function LoadData(Player)
	
	local DataFile = CreateDataFile(Player)
	AddDataIndex(Player, "Yen", "IntValue", 0)
	AddDataIndex(Player, "Physical_Resistance", "IntValue", 0)
	AddDataIndex(Player, "Physical_Strength", "IntValue", 0)
	AddDataIndex(Player, "Mental_Strength", "IntValue", 0)
	AddDataIndex(Player, "Mental_Resistance", "IntValue", 0)
	AddDataIndex(Player, "Reaction_Speed", "IntValue", 0)
	AddDataIndex(Player, "Endurance", "IntValue", 0)
	AddDataIndex(Player, "Flexibility", "IntValue", 0)
	AddDataIndex(Player, "LastPosition", "Vector3Value", Vector3.new(0,0,0))
	AddDataIndex(Player, "LastRotation", "Vector3Value", Vector3.new(0,0,0))
	AddNonSaveDataIndex(Player, "UseDebounce", "BoolValue", false)

	LoadDataIndexes(Player)
end

local function UpdatePlayerLastPosition(Player)
	local Stats = Player:FindFirstChild("Data")
	local HumanoidRootPart = Player.Character:FindFirstChild("HumanoidRootPart")
	if Stats and HumanoidRootPart then
		local LastPosition = Stats:FindFirstChild("LastPosition")
		LastPosition.Value = HumanoidRootPart.Position
		local LastRotation = Stats:FindFirstChild("LastRotation")
		LastRotation.Value = HumanoidRootPart.Rotation
	end
end

game.Players.PlayerAdded:Connect(function(Player)
	local GroupRank = Player:GetRoleInGroup(5019006)
	if GroupRank == nil or GroupRank == "Supporter" then
		Player:Kick("Sorry closed for testing")
		return
	else
		LoadData(Player)
		local Data = Player:WaitForChild("Data")
		local LastPosition = Data:WaitForChild("LastPosition")
		local LastRotation = Data:WaitForChild("LastRotation")
		local Character = game.Workspace:WaitForChild(Player.Name)
		local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")
		HumanoidRootPart.CFrame = CFrame.Angles(math.rad(LastRotation.Value.X), math.rad(LastRotation.Value.Y), math.rad(LastRotation.Value.Z)) + CFrame.new(LastPosition.Value.X, LastPosition.Value.Y, LastPosition.Value.Z).p
	end
end)

game.Players.PlayerRemoving:Connect(function(Player)
	if #game.Players:GetPlayers() ~= 1 then
		UpdatePlayerLastPosition(Player)
		SaveDataIndexes(Player)
	end
end)

game:BindToClose(function()
	for each,Player in pairs(game.Players:GetPlayers()) do
		UpdatePlayerLastPosition(Player)
		SaveDataIndexes(Player)
	end
end)

while wait(60) do
	for each,Player in pairs(game.Players:GetPlayers()) do
		UpdatePlayerLastPosition(Player)
		SaveDataIndexes(Player)
	end
end
7 Likes

So one suggestion I have is that you convert a player’s stored data into a simple dictionary like this:

DataTable[Player.Name] = {
    Yen = 0,
    Physical_Resistance = 0,
    Physical_Strength = 0,
    ...
}

Using value objects is fine, but it also adds code for creating and serializing the data. Using a table like this is not only super easy to setup (and modify) but it can also be saved to the DataStore with almost no modification (almost because of things like Vector3s which can’t be stored).

One downside of using a table is that will be more difficult for other scripts to see the stored information, however. You will need to use RemoteEvents and BindableEvents (however you should already be using these anyway so it can be argued that it just makes your codebase more consistent). But aside from that, they’re better in pretty much every way I can think of.

Also note that Instance.new(ClassName, Parent) is not very performant if you give it the Parent argument. So consider just setting the Instance’s parent after calling Instance.new(ClassName).

Regardless, if it ain’t broke, don’t fix it. You’ve spent the time making your current system and if it functions correctly then there’s no point in redoing it. In that case, save these tips for a future project :wink:

5 Likes