local ReplicatedStorage = game:GetService("ReplicatedStorage") local Players = game:GetService("Players") local AllQuestData = require(ReplicatedStorage:WaitForChild("ObjectiveData")) local eventsFolder = ReplicatedStorage:FindFirstChild("ObjectiveEvents") or Instance.new("Folder") eventsFolder.Name = "ObjectiveEvents" eventsFolder.Parent = ReplicatedStorage local function ensureEvent(name) local event = eventsFolder:FindFirstChild(name) or Instance.new("RemoteEvent") event.Name = name event.Parent = eventsFolder return event end local updateEvent = ensureEvent("UpdateObjective") local initEvent = ensureEvent("InitObjectives") local questStartEvent = ensureEvent("QuestStarted") local questCompleteEvent = ensureEvent("QuestCompleted") local playerData = {} local globalProgress = {} for _, quest in ipairs(AllQuestData) do globalProgress[quest.QuestName] = {} for _, obj in ipairs(quest.Objectives) do if obj.Type == "Global" then globalProgress[quest.QuestName][obj.Name] = false end end end local function hasTool(player, required) if not required then return true end local backpack = player:FindFirstChildOfClass("Backpack") local character = player.Character if not backpack or not character then return false end return (backpack:FindFirstChild(required) or character:FindFirstChild(required)) ~= nil end local function isQuestComplete(player, questName) local data = playerData[player.UserId] if not data or not data.Quests[questName] then return false end for _, completed in pairs(data.Quests[questName].Completed) do if not completed then return false end end return true end local function startQuestline(player, questName) local data = playerData[player.UserId] if not data then return end if data.Quests[questName] and data.Quests[questName].Started then initEvent:FireClient(player, questName, data.Quests[questName].Completed) return end local quest for _, q in ipairs(AllQuestData) do if q.QuestName == questName then quest = q break end end if not quest then warn("Questline not found:", questName) return end data.Quests[questName] = { Started = true, Completed = {} } for _, obj in ipairs(quest.Objectives) do data.Quests[questName].Completed[obj.Name] = globalProgress[quest.QuestName][obj.Name] or false end initEvent:FireClient(player, questName, data.Quests[questName].Completed) questStartEvent:FireClient(player, questName) end local function startNextObjective(player, quest, currentObj) local nextName = currentObj.NextObjective if not nextName then return end local data = playerData[player.UserId] if not data or not data.Quests[quest.QuestName] then return end if data.Quests[quest.QuestName].Completed[nextName] ~= nil then data.Quests[quest.QuestName].Completed[nextName] = false updateEvent:FireClient(player, quest.QuestName, nextName, false) end end local function completeObjective(player, quest, obj) local questName, objName = quest.QuestName, obj.Name local data = playerData[player.UserId] if not data or not data.Quests[questName] then return end if data.Quests[questName].Completed[objName] then return end if obj.Type == "Global" then if not globalProgress[questName][objName] then globalProgress[questName][objName] = true end data.Quests[questName].Completed[objName] = true for _, plr in ipairs(Players:GetPlayers()) do local pdata = playerData[plr.UserId] if pdata and pdata.Quests[questName] and pdata.Quests[questName].Started then if not pdata.Quests[questName].Completed[objName] then pdata.Quests[questName].Completed[objName] = true updateEvent:FireClient(plr, questName, objName, true) end end end else data.Quests[questName].Completed[objName] = true updateEvent:FireClient(player, questName, objName, true) end if isQuestComplete(player, questName) then questCompleteEvent:FireClient(player, questName) end if obj.NextQuestline then task.wait(1) startQuestline(player, obj.NextQuestline) elseif obj.EndQuest then questCompleteEvent:FireClient(player, questName) else startNextObjective(player, quest, obj) end end for _, quest in ipairs(AllQuestData) do for _, obj in ipairs(quest.Objectives) do local trigger = obj.Trigger if not trigger then continue end if trigger:IsA("ProximityPrompt") then trigger.Triggered:Connect(function(player) if hasTool(player, obj.Requires) then completeObjective(player, quest, obj) end end) elseif trigger:IsA("BasePart") then trigger.Touched:Connect(function(hit) local player = Players:GetPlayerFromCharacter(hit.Parent) if player and hasTool(player, obj.Requires) then completeObjective(player, quest, obj) end end) elseif obj.TriggerType == "CanCollideChange" then trigger:GetPropertyChangedSignal("CanCollide"):Connect(function() if trigger.CanCollide then for _, plr in ipairs(Players:GetPlayers()) do local pdata = playerData[plr.UserId] if pdata and pdata.Quests[quest.QuestName] and pdata.Quests[quest.QuestName].Started then completeObjective(plr, quest, obj) end end end end) elseif obj.TriggerType == "ColorChange" and obj.ColorTarget then trigger:GetPropertyChangedSignal("Color"):Connect(function() if trigger.Color == obj.ColorTarget then for _, plr in ipairs(Players:GetPlayers()) do local pdata = playerData[plr.UserId] if pdata and pdata.Quests[quest.QuestName] and pdata.Quests[quest.QuestName].Started then completeObjective(plr, quest, obj) end end end end) else warn("Unsupported trigger type for", obj.Name) end end end for _, quest in ipairs(AllQuestData) do if quest.StartTrigger then local start = quest.StartTrigger local function startQuest(player) startQuestline(player, quest.QuestName) end if start:IsA("BasePart") then start.Touched:Connect(function(hit) local player = Players:GetPlayerFromCharacter(hit.Parent) if player then startQuest(player) end end) elseif start:IsA("ProximityPrompt") then start.Triggered:Connect(startQuest) else warn("Invalid StartTrigger type for quest:", quest.QuestName) end end end Players.PlayerAdded:Connect(function(player) playerData[player.UserId] = playerData[player.UserId] or { Quests = {} } player.CharacterAdded:Connect(function() task.wait(1) local data = playerData[player.UserId] if not data then return end for questName, questInfo in pairs(data.Quests) do if questInfo.Started then for objName, _ in pairs(questInfo.Completed) do if globalProgress[questName] and globalProgress[questName][objName] then questInfo.Completed[objName] = true end end initEvent:FireClient(player, questName, questInfo.Completed) end end end) end) Players.PlayerRemoving:Connect(function(player) playerData[player.UserId] = nil end)