Is my Data script optimal?

Hey just wondering if this ds script is optimal meaning 2 things it saves data no matter what and it is not exploitable

local DataStoreService = game:GetService("DataStoreService")
local DataStoreModule = require(1936396537) -- DataStore2 Module

-- Define the data keys and their default values
local dataKeys = {
	-- leaderstats
	{name = "Cash", defaultValue = 0, dataType = "IntValue", parent = "leaderstats"},
	{name = "Kills", defaultValue = 0, dataType = "IntValue", parent = "leaderstats"},
	{name = "Kill Streak", defaultValue = 0, dataType = "IntValue", parent = "leaderstats"},
	{name = "KDR", defaultValue = 0, dataType = "NumberValue", parent = "leaderstats"},
	-- non leaderstats stats
	{name = "Deaths", defaultValue = 0, dataType = "IntValue", parent = "nonleaderstats"},
	{name = "Highest Kill Streak", defaultValue = 0, dataType = "IntValue", parent = "nonleaderstats"},
	{name = "Highest Cash", defaultValue = 0, dataType = "IntValue", parent = "nonleaderstats"},
	{name = "Reserved Ammo", defaultValue = 15, dataType = "IntValue", parent = "nonleaderstats", adjust = function(value) return (value or 15) + 5 end},
	-- Settings
	{name = "Instant Respawn", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "Adjust Skope", defaultValue = 4.5, dataType = "NumberValue", parent = "nonleaderstats"},
	{name = "Infinite Ammo", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "Disable Pop Ups", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "Infinite Jump", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "Double Cash", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "Music", defaultValue = true, dataType = "BoolValue", parent = "nonleaderstats"}, -- Specific Data
	-- Cosmetics Equipped
	{name = "Chat Tag", defaultValue = 0, dataType = "NumberValue", parent = "nonleaderstats"},
	{name = "Gun Skin", defaultValue = 0, dataType = "NumberValue", parent = "nonleaderstats"},
	{name = "Emote", defaultValue = 0, dataType = "NumberValue", parent = "nonleaderstats"},
	-- Chat Tags Owned
	{name = "CT1", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "CT2", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "CT3", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "CT4", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "CT5", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "CT6", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "CT7", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "CT8", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "CT9", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	-- Gun Skins Owned
	{name = "GS1", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "GS2", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "GS3", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "GS4", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "GS5", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "GS6", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "GS7", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "GS8", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "GS9", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	-- Emotes Owned
	{name = "E1", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "E2", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "E3", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "E4", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "E5", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "E6", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "E7", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "E8", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"},
	{name = "E9", defaultValue = false, dataType = "BoolValue", parent = "nonleaderstats"}
}

-- Combine the keys in DataStoreModule
local keys = {}
for _, data in ipairs(dataKeys) do
	table.insert(keys, data.name)
end
DataStoreModule.Combine("MasterKey", unpack(keys))

local function createValueInstance(data, parent)
	local valueInstance = Instance.new(data.dataType)
	valueInstance.Name = data.name:gsub("^%l", string.upper) -- Capitalizes first letter for display
	valueInstance.Parent = parent
	return valueInstance
end

local function loadDataAndConnect(data, player, valueInstance)
	local dataStore = DataStoreModule(data.name, player)
	local success, storedValue = pcall(function()
		return dataStore:Get()
	end)

	if success then

		if storedValue == nil then
			-- Data doesn't exist, use default value
			valueInstance.Value = data.defaultValue
		else
			local valueType = typeof(storedValue)
			local expectedType = typeof(data.defaultValue)

			if valueType == expectedType then
				valueInstance.Value = storedValue
			else
				valueInstance.Value = data.defaultValue
			end
		end
	end

	valueInstance:GetPropertyChangedSignal("Value"):Connect(function()
		local success, errorMsg = pcall(function()
			dataStore:Set(valueInstance.Value)
		end)
	end)
end







game.Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder", player)
	leaderstats.Name = "leaderstats"

	local nonleaderstats = Instance.new("Folder", player)
	nonleaderstats.Name = "nonleaderstats"

	for _, data in ipairs(dataKeys) do
		local parent = data.parent == "leaderstats" and leaderstats or nonleaderstats
		local valueInstance = createValueInstance(data, parent)
		loadDataAndConnect(data, player, valueInstance)
	end
end)

i amnot very good at data stores but you should use profile service for serverlocking to prevent players from losing data
imagine this
– player joined server a and got 100 cash
– player left server a and the server is saving his data
– player joins server b but server a didnot finish saving his data
– server b will get 0 cash from the datastore and loads his data
– the player got 0 cash and server a saved his data but its too late
– i amnot good at explanation if this wasnot clear you can check server locking on the forum or yt

1 Like

Since it’s being handled by the server, the data itself should be fine. Just double check there aren’t any easily exploitable remote events which can be fired by exploiters to artificially boost their currency,

There can be improvement to one variable, instead of parent, use this:

Note: this also removes name as it’s index, which is faster than using table methoods

local data = {
    ["leaderstats"] = {
        Coins = {Your data},
        Gems = {Your data}
    },
    ["inventory"] = {
        -- data
    }
}

Another point, you can remove third argument too! you have to structure your game in a way where number value is handled as integer, you see you can divide integer, ik it might be visually wrong or not comfortable, but if your game use percents number then go for it, later you can have table like that:

local ClassNames = {
    ["string"] = "StringValue",
    ["number"] = "IntValue",
    ["boolean"] = "BoolValue"
}

local newValue = Instance.new(ClassNames[typeof(Give default value here)])
-- code

by this you turned giant table of repeating values into

local data = {
    ["leaderstats"] = {
        Coins = 0,
        Gems = 0
    },
    ["settings"] = {
        IsPlayerOp = false,
        DoPlayerLikePizza = true,
        IsRobloxCool = true
    }
}

Profile service is OK, but you can create something like that using UpdateAsync & AutoSave combo with BindToClose in case of shutdown

1 Like