Timer and quest always being reset

My goal here is so that whenever the player advances to the next quest upon completion their timer still goes down normally and the quest name is correctly updated for the player. And that the timer and quest is only reset once the timer hit’s 0.

The issue is that whenever the player completes their quest and is assigned the next quest its making the timer reset and the first quest for that daily or weekly or monthly quest to be there but the thing is the second quest is still there but the first quest is too its weird kind of like a glitching back and forth.

I’ve tried not calling the reset intervals every time the player is assigned the next quest upon completion so that it’s not constantly resetting it but then that just gave me a bunch of nil errors and didn’t work.

After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

Quest Manager server script

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

local QuestsModule = require(ReplicatedStorage:WaitForChild("Modules"):WaitForChild("QuestsModule"))
local QuestsDataStore = DataStoreService:GetDataStore("QuestsResetTimes")

-- Reset intervals for daily, weekly, and monthly quests
local RESET_INTERVALS = {
	Daily = 86400,   -- 24 hours
	Weekly = 604800, -- 7 days
	Monthly = 2592000 -- 30 days
}

-- Function to retrieve the last reset time for the player
local function getLastResetTime(player, questType)
	local key = player.UserId .. "_" .. questType
	local success, result = pcall(function()
		return QuestsDataStore:GetAsync(key) or os.time() - RESET_INTERVALS[questType]
	end)
	if not success then
		warn("Failed to get reset time: " .. result)
		return os.time() - RESET_INTERVALS[questType]
	end
	return result
end

-- Function to save the reset time for the player
local function saveResetTime(player, questType)
	local key = player.UserId .. "_" .. questType
	pcall(function()
		QuestsDataStore:SetAsync(key, os.time())
	end)
end

local function checkQuestCompletion(player, questType)
	local currentQuest = QuestsModule:GetPlayerQuest(player, questType)
	local completed = true

	for stat, requiredAmount in pairs(currentQuest.Stats) do
		local playerStat = player:FindFirstChild("PhysicalStats"):FindFirstChild(stat)
		if playerStat then
			local playerStatValue = tonumber(playerStat.Value)
			if not playerStatValue or playerStatValue < requiredAmount then
				completed = false
				break
			end
		else
			completed = false
			break
		end
	end

	if completed then
		QuestsModule:GrantQuestRewards(player, questType)
		QuestsModule:AssignNextQuest(player, questType)
		-- Notify client about quest completion
		local timeRemaining = RESET_INTERVALS[questType]
		ReplicatedStorage:WaitForChild("QuestUpdate"):FireClient(player, questType, timeRemaining)
	end
end

local function initializePlayerQuests(player)
	for questType, _ in pairs(RESET_INTERVALS) do
		local lastReset = getLastResetTime(player, questType)
		local resetTime = lastReset + RESET_INTERVALS[questType]
		local timeRemaining = math.max(resetTime - os.time(), 0)

		-- Check if the quest should be reset (if timeRemaining is 0)
		if timeRemaining == 0 then
			saveResetTime(player, questType)
			QuestsModule:InitializeQuest(player, questType) -- Initialize quest
		else
			-- Send the time remaining to the client
			ReplicatedStorage:WaitForChild("QuestUpdate"):FireClient(player, questType, timeRemaining)
		end

		-- Ensure quest progression/completion is checked regularly
		checkQuestCompletion(player, questType)
	end
end

-- Function to connect to player stat changes
local function connectStatChangeListeners(player)
	for _, stat in ipairs({"Strength", "Speed", "Psychic", "Endurance"}) do
		local statObj = player:FindFirstChild("PhysicalStats"):FindFirstChild(stat)
		if statObj then
			statObj.Changed:Connect(function()
				for questType, _ in pairs(RESET_INTERVALS) do
					checkQuestCompletion(player, questType)
				end
			end)
		else
			warn("Stat not found: " .. stat)
		end
	end
end

-- Event when a player joins the game
Players.PlayerAdded:Connect(function(player)
	-- Initialize quests and connect stat change listeners
	initializePlayerQuests(player)
	connectStatChangeListeners(player)
end)

Quest module replicated storage

--// Services
local TweenService = game:GetService("TweenService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RSmodules = ReplicatedStorage:WaitForChild("Modules")
local BigNum = require(RSmodules:WaitForChild("BigNum"))

local QuestsModule = {}
QuestsModule.__index = QuestsModule

-- Reset intervals for each quest type (in seconds)
local RESET_INTERVALS = {
	Daily = 86400,   -- 1 day
	Weekly = 604800, -- 1 week
	Monthly = 2592000 -- 1 month
}

-- Quests Data
QuestsModule.QuestData = {
	Daily = {
		{ Name = "Daily Quest 1", Stats = { Strength = 500, Speed = 500, Psychic = 500, Endurance = 500 }, Rewards = { Tokens = 100 } },
		{ Name = "Daily Quest 2", Stats = { Strength = 1000, Speed = 1000, Psychic = 1000, Endurance = 1000 }, Rewards = { Tokens = 200 } },
		{ Name = "Daily Quest 3", Stats = { Strength = 1500, Speed = 1500, Psychic = 1500, Endurance = 1500 }, Rewards = { Tokens = 300 } },
		{ Name = "Daily Quest 4", Stats = { Strength = 2000, Speed = 2000, Psychic = 2000, Endurance = 2000 }, Rewards = { Tokens = 400 } },
	},
	Weekly = {
		{ Name = "Weekly Quest 1", Stats = { Strength = 2000, Speed = 2000, Psychic = 2000, Endurance = 2000 }, Rewards = { Tokens = 500 } },
		{ Name = "Weekly Quest 2", Stats = { Strength = 3000, Speed = 3000, Psychic = 3000, Endurance = 3000 }, Rewards = { Tokens = 700 } },
		{ Name = "Weekly Quest", Stats = { Strength = 4000, Speed = 4000, Psychic = 4000, Endurance = 4000 }, Rewards = { Tokens = 900 } },
		{ Name = "Weekly Quest", Stats = { Strength = 5000, Speed = 5000, Psychic = 5000, Endurance = 5000 }, Rewards = { Tokens = 1100 } },
	},
	Monthly = {
		{ Name = "Monthly Quest", Stats = { Strength = 5000, Speed = 5000, Psychic = 5000, Endurance = 5000 }, Rewards = { Tokens = 1000 } },
		{ Name = "Monthly Quest", Stats = { Strength = 7000, Speed = 7000, Psychic = 7000, Endurance = 7000 }, Rewards = { Tokens = 1200 } },
		{ Name = "Monthly Quest", Stats = { Strength = 9000, Speed = 9000, Psychic = 9000, Endurance = 9000 }, Rewards = { Tokens = 1400 } },
		{ Name = "Monthly Quest", Stats = { Strength = 11000, Speed = 11000, Psychic = 11000, Endurance = 11000 }, Rewards = { Tokens = 1600 } },
	}
}

-- Utility function to abbreviate numbers
local function abbreviateNumber(value)
	local num = tonumber(value)
	if not num then return value end
	local suffixes = { "", "K", "M", "B", "T" }
	local index = 1
	while num >= 1000 and index < #suffixes do
		num = num / 1000
		index = index + 1
	end
	return string.format("%.2f", num):gsub("%.?0*$", "") .. suffixes[index]
end

-- Utility function to format time remaining
local function formatTime(seconds, questType)
	if questType == "Monthly" then
		local days = math.floor(seconds / 86400)
		local hours = math.floor((seconds % 86400) / 3600)
		local minutes = math.floor((seconds % 3600) / 60)
		return string.format("%d Days %02d Hours %02d Minutes", days, hours, minutes)
	elseif questType == "Weekly" then
		local days = math.floor(seconds / 86400)
		local hours = math.floor((seconds % 86400) / 3600)
		local minutes = math.floor((seconds % 3600) / 60)
		return string.format("%d Days %02d Hours %02d Minutes", days, hours, minutes)
	else
		local hours = math.floor(seconds / 3600)
		local minutes = math.floor((seconds % 3600) / 60)
		local secs = seconds % 60
		return string.format("%02d:%02d:%02d", hours, minutes, secs)
	end
end

-- Function to retrieve the player's current quest
function QuestsModule:GetPlayerQuest(player, questType)
	local questIndex = player:GetAttribute(questType .. "QuestIndex") or 1
	return self.QuestData[questType][questIndex]
end

-- Function to assign the next quest in the quest progression
function QuestsModule:AssignNextQuest(player, questType)
	local currentQuestIndex = player:GetAttribute(questType .. "QuestIndex") or 1

	-- Check if the current quest is the last one
	if currentQuestIndex < #self.QuestData[questType] then
		local nextQuestIndex = currentQuestIndex + 1
		player:SetAttribute(questType .. "QuestIndex", nextQuestIndex)
	else
		print(player.Name .. " has completed all " .. questType .. " quests.")
		-- Handle the end of quests here, if needed
	end
end


-- Function to grant rewards to the player upon quest completion
function QuestsModule:GrantQuestRewards(player, questType)
	local currentQuest = self:GetPlayerQuest(player, questType)
	local rewardTokens = currentQuest.Rewards.Tokens

	-- Update the player's tokens
	local tokens = player:FindFirstChild("Currency"):FindFirstChild("Tokens")
	if tokens then
		tokens.Value = tokens.Value + rewardTokens
	else
		warn("Tokens not found for player: " .. player.Name)
	end
end

function QuestsModule:UpdateQuestUI(player, questType, timeRemaining)
	local playerGui = player:FindFirstChild("PlayerGui")
	if not playerGui then
		warn("PlayerGui not found for player: " .. player.Name)
		return
	end

	local questsGui = playerGui:FindFirstChild("Quests")
	if not questsGui then
		warn("Quests GUI not found for player: " .. player.Name)
		return
	end

	local questsFrame = questsGui:FindFirstChild("QuestsFrame")
	if not questsFrame then
		warn("QuestsFrame not found for player: " .. player.Name)
		return
	end

	local bg = questsFrame:FindFirstChild("Bg")
	if not bg then
		warn("Bg not found for player: " .. player.Name)
		return
	end

	local questCategory = bg:FindFirstChild(questType .. "Quests")
	if not questCategory then
		warn("Quest category not found for player: " .. player.Name .. " - " .. questType .. "Quests")
		return
	end

	local frame = questCategory:FindFirstChild("Frame")
	local baseProgressFrame = frame and frame:FindFirstChild("BaseProgressFrame")
	local rewardsFrame = frame and frame:FindFirstChild("RewardsFrame")
	local questNameLabel = questCategory:FindFirstChild("QuestName")

	if not (questNameLabel and frame and baseProgressFrame and rewardsFrame) then
		warn("Quest UI components missing for player: " .. player.Name)
		return
	end

	-- Clear previous content, but preserve BaseProgressFrame and RewardsFrame
	for _, child in ipairs(frame:GetChildren()) do
		if child:IsA("Frame") and child.Name ~= "BaseProgressFrame" and child.Name ~= "RewardsFrame" then
			child:Destroy()
		end
	end

	-- Get the current quest for the player
	local quest = self:GetPlayerQuest(player, questType)

	-- Create a new BaseProgressFrame for each stat requirement
	for stat, req in pairs(quest.Stats) do
		local clone = baseProgressFrame:Clone()
		local progLabel = clone:FindFirstChild("ProgressLabel")
		local fill = clone:FindFirstChild("Fill")

		if not (progLabel and fill) then
			warn("Progress elements missing for player: " .. player.Name)
			return
		end

		-- Update quest progress
		local playerStat = player:FindFirstChild("PhysicalStats"):FindFirstChild(stat)
		if not playerStat then
			warn("Player stat missing for player: " .. player.Name .. " - " .. stat)
			return
		end

		progLabel.Text = abbreviateNumber(playerStat.Value) .. "/" .. abbreviateNumber(req) .. " " .. stat
		local ratio = math.min(playerStat.Value / req, 1)

		local tweenInfo = TweenInfo.new(1.5, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut)
		local tween = TweenService:Create(fill, tweenInfo, {Size = UDim2.new(ratio, 0, 1, 0)})
		tween:Play()

		-- Update progress bar as the player's stat changes
		playerStat.Changed:Connect(function()
			progLabel.Text = abbreviateNumber(playerStat.Value) .. "/" .. abbreviateNumber(req) .. " " .. stat
			ratio = math.min(playerStat.Value / req, 1)
			local tweenUpdate = TweenService:Create(fill, tweenInfo, {Size = UDim2.new(ratio, 0, 1, 0)})
			tweenUpdate:Play()
		end)

		clone.Name = stat .. "ProgBar"
		clone.Visible = true
		clone.Parent = frame
	end

	-- Set quest name and reward with timer
	local formattedTime = formatTime(timeRemaining, questType)
	questNameLabel.Text = quest.Name .. " - Resets in: " .. formattedTime

	-- Update Rewards Frame
	local tokensLabel = rewardsFrame:FindFirstChild("TokensLabel")
	if not tokensLabel then
		tokensLabel = Instance.new("TextLabel")
		tokensLabel.Name = "TokensLabel"
		tokensLabel.Size = UDim2.new(1, 0, 1, 0)
		tokensLabel.BackgroundTransparency = 1
		tokensLabel.TextColor3 = Color3.new(1, 1, 1)
		tokensLabel.TextScaled = true
		tokensLabel.Parent = rewardsFrame
	end
	tokensLabel.Text = "Reward: " .. abbreviateNumber(quest.Rewards.Tokens) .. " Tokens"

	rewardsFrame.Visible = true

	-- Function to update timer label periodically
	local function updateTimer()
		while timeRemaining > 0 do
			timeRemaining = timeRemaining - 1
			local formattedTime = formatTime(timeRemaining, questType)
			questNameLabel.Text = quest.Name .. " - Resets in: " .. formattedTime
			wait(1) -- Update every second
		end

		-- Ensure the text shows "Expired" or some indicator when time is up
		questNameLabel.Text = quest.Name .. " - Resets in: 00:00:00"

	end

	-- Start updating the timer
	coroutine.wrap(updateTimer)()
end

function QuestsModule:CheckQuestCompletion(player, questType)
	local currentQuest = self:GetPlayerQuest(player, questType)
	local completed = true

	-- Loop through required stats and check completion
	for stat, requiredAmount in pairs(currentQuest.Stats) do
		local playerStat = player:FindFirstChild("PhysicalStats"):FindFirstChild(stat)
		if playerStat then
			local playerStatValue = tonumber(playerStat.Value)
			if not playerStatValue or playerStatValue < requiredAmount then
				completed = false
				break
			end
		else
			completed = false
			break
		end
	end

	if completed then
		-- Grant rewards and check if there's a next quest
		self:GrantQuestRewards(player, questType)
		self:AssignNextQuest(player, questType)  -- This will check if a next quest can be assigned
		-- Update the quest UI immediately to reflect the changes
		local timeRemaining = RESET_INTERVALS[questType]
		self:UpdateQuestUI(player, questType, timeRemaining)
	end
end

-- Function to initialize a new quest for the player
function QuestsModule:InitializeQuest(player, questType)
	player:SetAttribute(questType .. "QuestIndex", 1) -- Start at the first quest
	local timeRemaining = RESET_INTERVALS[questType]
	self:UpdateQuestUI(player, questType, timeRemaining)
end

return QuestsModule

Local gui even listener to display

--// Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local QuestsModule = require(ReplicatedStorage:WaitForChild("Modules"):WaitForChild("QuestsModule"))

local player = Players.LocalPlayer

-- Listen for quest updates from the server
ReplicatedStorage:WaitForChild("QuestUpdate").OnClientEvent:Connect(function(questType, timeRemaining)
    print("Received QuestUpdate for questType:", questType, "Time Remaining:", timeRemaining)
    if player and player.Character then
        QuestsModule:UpdateQuestUI(player, questType, timeRemaining)
    else
        warn("Player or Player Character not found")
    end
end)


-- Request quest update when the player character respawns
player.CharacterAdded:Connect(function()
	if player then
		ReplicatedStorage:WaitForChild("RequestQuestUpdate"):FireServer()
	else
		warn("Player not found during CharacterAdded event")
	end
end)

-- Initial request for quest update when the game starts
if player then
	ReplicatedStorage:WaitForChild("RequestQuestUpdate"):FireServer()
else
	warn("Player not found during initial request")
end

2 Likes