Warn module somehow isn't saving warns properly

Okay so the table has data at the time of saving. So then something might be going wrong with SetAsync. The documentation shows the key should be a string but you’re passing in a number. Not sure if would affect it but try using a string instead?

Still not saving the following which is weird.

I Tested This Code in my game and it work

Module Script :

local ValidReasons = {
	"Inapropriate Audio",
	"Detain Abuse",
	"Admin Abuse",
	"Blocking",
	"Trolling",
	"Btools Abuse",
	"Fake Donation",
	"Exploiting",
	"Break Roblox TOS",
	"Moderation Bypass",
}

-- Services
local DatastoreService = game:GetService("DataStoreService")
local HttpService = game:GetService("HttpService")
local PlayersServ = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Warning Datastore
local WarningDataStore = DatastoreService:GetDataStore("Warnings")

-- NumberOfWarningsRequiredForBan
local RequiredWarnings = 4

-- Store Warnings in a roblox table
local ActiveWarningListForPlayers = {}

local System = {}

local function WarnUser(UserId:number,Reason:string,sender:string)
	if not table.find(ValidReasons,Reason)  then
		return
	end

	local self = System

	local warningsamount = 0

	for i,v in ActiveWarningListForPlayers[UserId] do
		warningsamount +=1
	end

	warningsamount += 1

	ActiveWarningListForPlayers[UserId][warningsamount] = {
		Sender = sender,
		ReasonGiven  = Reason
	} 

	if warningsamount >= RequiredWarnings  then
		ActiveWarningListForPlayers[UserId] = {}

		-- Ban the player temporary with banasync
		PlayersServ:BanAsync({
			UserIds = {UserId},
			ApplyToUniverse = true,
			Duration = 250,
			DisplayReason =  "You have reachen the maximum amount of warnings allowed, Your last warning is "..Reason..".   If you think this is a mistake please contact our support team",
			PrivateReason = "Banned for max warning by admin for "..Reason,
			ExcludeAltAccounts = true
		})
		--warn("Player has been banned for "..reason.." by admin")


		self:SaveWarnings(UserId)

		if PlayersServ:GetPlayerByUserId(UserId) then
			PlayersServ:GetPlayerByUserId(UserId):Kick( "A moderation action had been taken, please rejoin to review it.")
		end

		task.wait(2)
		ActiveWarningListForPlayers[UserId] = nil

		return
	end


	--if PlayersServ:GetPlayerByUserId(UserId) then
	--	ReplicatedStorage.RemoteEvents.Warnings:FireClient(PlayersServ:GetPlayerByUserId(UserId),sender,Reason)
	--end
end

function System:Warn(UserId:number,Reason:string,sender:string)
	task.spawn(WarnUser,UserId,Reason,sender)   
end

function System:RemoveWarn(UserId:number,warnnumber:number)
	if not ActiveWarningListForPlayers[UserId][warnnumber] then
		return
	end

	local amountofwarns = 0

	for i,v in ActiveWarningListForPlayers[UserId] do
		amountofwarns += 1
	end

	if amountofwarns == 0 then
		return
	end

	local lastwarns = {}
	local afterwarns = {}

	for i,v in ActiveWarningListForPlayers[UserId] do
		if i < warnnumber  then
			lastwarns[i] = v
		end

		if i > warnnumber then
			afterwarns[i] = v
		end
	end

	-- Reclassify the warn by removing 1 from every i in afterwarns then put them back
	ActiveWarningListForPlayers[UserId] = {}

	for i,v in lastwarns do
		ActiveWarningListForPlayers[UserId][i] = v
	end

	for i,v in afterwarns do
		ActiveWarningListForPlayers[UserId][i-1] = v
	end

	-- Avoid memory leaks
	lastwarns = nil
	afterwarns = nil
end

function System:GetWarnings(UserId:number)
	-- Load from json to the table
	local Success, Data = pcall(function()
		return WarningDataStore:GetAsync(UserId)
	end)

	if Success and Data then
		ActiveWarningListForPlayers[UserId] = HttpService:JSONDecode(Data)
		print(Data)
	end

	if Success and not Data then
		ActiveWarningListForPlayers[UserId] = {}
	end

	return ActiveWarningListForPlayers[UserId]
end

function System:SaveWarnings(UserId:number)

	if not ActiveWarningListForPlayers[UserId] then
		warn("Save Operation failed. Warnings wasnt on the roblox table which might cause dataloss")
		return
	end 

	-- Use json to convert into string
	local Success, Error = pcall(function()
		WarningDataStore:SetAsync(UserId,HttpService:JSONEncode(ActiveWarningListForPlayers[UserId]))
	end)

	if not Success then
		warn("Failed to Save Warnings for "..UserId.." | Reason: "..Error)
	end

end

function System:GetAllWarns()
	
	return ActiveWarningListForPlayers
	
end

function System:GetPLayerWarns(UserId : number)
	
	return ActiveWarningListForPlayers[UserId]
	
end

return System

ServerScript :

-- Services
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Modules
local ModerationModule = require(ReplicatedStorage.ModuleScript)

Players.PlayerAdded:Connect(function(player)
	ModerationModule:GetWarnings(player.UserId)
	task.wait(5)
	warn("we Warn him")
	ModerationModule:Warn(player.UserId, "Exploiting", "Icke")
	task.wait(0.5)
	warn(ModerationModule:GetAllWarns())
end)

Players.PlayerRemoving:Connect(function(player)
	ModerationModule:SaveWarnings(player.UserId)
end)

Console Log :

All Date from the DataStore

00:54:36.563 [{“ReasonGiven”:“Exploiting”,“Sender”:“Icke”}] - Server - ModuleScript:140

**Data after Warn **
00:54:42.087 ▼ {
[2492399418] = ▼ {
[1] = ▼ {
[“ReasonGiven”] = “Exploiting”,
[“Sender”] = “Icke”
},
[2] = ▼ {
[“ReasonGiven”] = “Exploiting”,
[“Sender”] = “Icke”
}
}
} - Server - Script:14

I just removed the RankingSystem and triggert it from the server script. How do you want to trigger it. From Client or Server Side?

Obviously server side. I don’t want exploiters to cause mass warning in the server and having a protection allowing to warn abusers.

Then you can use this moduleScript which can be in ReplicatedStorage because its asked if we are sending it from a server.

local ValidReasons = {
	"Inapropriate Audio",
	"Detain Abuse",
	"Admin Abuse",
	"Blocking",
	"Trolling",
	"Btools Abuse",
	"Fake Donation",
	"Exploiting",
	"Break Roblox TOS",
	"Moderation Bypass",
}

-- Services
local DatastoreService = game:GetService("DataStoreService")
local HttpService = game:GetService("HttpService")
local PlayersServ = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

-- Warning Datastore
local WarningDataStore = DatastoreService:GetDataStore("Warnings")

-- NumberOfWarningsRequiredForBan
local RequiredWarnings = 4

-- Store Warnings in a roblox table
local ActiveWarningListForPlayers = {}

local System = {}

local function WarnUser(UserId:number,Reason:string,sender:string)
	if not table.find(ValidReasons,Reason)  then
		return
	end

	local self = System

	local warningsamount = 0

	for i,v in ActiveWarningListForPlayers[UserId] do
		warningsamount +=1
	end

	warningsamount += 1

	ActiveWarningListForPlayers[UserId][warningsamount] = {
		Sender = sender,
		ReasonGiven  = Reason
	} 

	if warningsamount >= RequiredWarnings  then
		ActiveWarningListForPlayers[UserId] = {}

		-- Ban the player temporary with banasync
		PlayersServ:BanAsync({
			UserIds = {UserId},
			ApplyToUniverse = true,
			Duration = 250,
			DisplayReason =  "You have reachen the maximum amount of warnings allowed, Your last warning is "..Reason..".   If you think this is a mistake please contact our support team",
			PrivateReason = "Banned for max warning by admin for "..Reason,
			ExcludeAltAccounts = true
		})
		--warn("Player has been banned for "..reason.." by admin")


		self:SaveWarnings(UserId)

		if PlayersServ:GetPlayerByUserId(UserId) then
			PlayersServ:GetPlayerByUserId(UserId):Kick( "A moderation action had been taken, please rejoin to review it.")
		end

		task.wait(2)
		ActiveWarningListForPlayers[UserId] = nil

		return
	end


	--if PlayersServ:GetPlayerByUserId(UserId) then
	--	ReplicatedStorage.RemoteEvents.Warnings:FireClient(PlayersServ:GetPlayerByUserId(UserId),sender,Reason)
	--end
end

function System:Warn(UserId:number,Reason:string,sender:string)
	
	if RunService:IsClient() then warn(UserId .. " try to exploit the game") return end
	
	task.spawn(WarnUser,UserId,Reason,sender)   
	
end

function System:RemoveWarn(UserId:number,warnnumber:number)
	
	if RunService:IsClient() then warn(UserId .. " try to exploit the game") return end
	
	if not ActiveWarningListForPlayers[UserId][warnnumber] then
		return
	end

	local amountofwarns = 0

	for i,v in ActiveWarningListForPlayers[UserId] do
		amountofwarns += 1
	end

	if amountofwarns == 0 then
		return
	end

	local lastwarns = {}
	local afterwarns = {}

	for i,v in ActiveWarningListForPlayers[UserId] do
		if i < warnnumber  then
			lastwarns[i] = v
		end

		if i > warnnumber then
			afterwarns[i] = v
		end
	end

	-- Reclassify the warn by removing 1 from every i in afterwarns then put them back
	ActiveWarningListForPlayers[UserId] = {}

	for i,v in lastwarns do
		ActiveWarningListForPlayers[UserId][i] = v
	end

	for i,v in afterwarns do
		ActiveWarningListForPlayers[UserId][i-1] = v
	end

	-- Avoid memory leaks
	lastwarns = nil
	afterwarns = nil
end

function System:GetWarnings(UserId:number)
	-- Load from json to the table
	
	if RunService:IsClient() then warn(UserId .. " try to exploit the game") return end
	
	local Success, Data = pcall(function()
		return WarningDataStore:GetAsync(UserId)
	end)

	if Success and Data then
		ActiveWarningListForPlayers[UserId] = HttpService:JSONDecode(Data)
		print(Data)
	end

	if Success and not Data then
		ActiveWarningListForPlayers[UserId] = {}
	end

	return ActiveWarningListForPlayers[UserId]
end

function System:SaveWarnings(UserId:number)

	if RunService:IsClient() then warn(UserId .. " try to exploit the game") return end

	if not ActiveWarningListForPlayers[UserId] then
		warn("Save Operation failed. Warnings wasnt on the roblox table which might cause dataloss")
		return
	end 

	-- Use json to convert into string
	local Success, Error = pcall(function()
		WarningDataStore:SetAsync(UserId,HttpService:JSONEncode(ActiveWarningListForPlayers[UserId]))
	end)

	if not Success then
		warn("Failed to Save Warnings for "..UserId.." | Reason: "..Error)
	end

end

function System:GetAllWarns()
	
	return ActiveWarningListForPlayers
	
end

function System:GetPLayerWarns(UserId : number)
	
	return ActiveWarningListForPlayers[UserId]
	
end

return System

Now you can Just add it in a ServerScript and just call the Warn function or hear on a remoteFunctions from the Server to Trigger the Function (if you have a warn GUI where the Admin can type all stuff).

Yea I also just took your original scripts and ran it in my place, and it’s working fine. I’m not sure tbh why you’re seeing issues with loading :thinking:

Just realised it worked fine in game but not in studio and i dont know why