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