I’m gonna have a service for creating tasks when a player asks for them, and there will be presets available, but for now you can create a blank task and objective, and then just add the objectives you want to the task and then activate it.
Task
-- TaskClass.lua
-- Galicate @ 11:09 12/10/2024
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Signal = require(ReplicatedStorage.Util.Signal)
local TaskClass = {}
TaskClass.__index = TaskClass
-- Returns a new <code>Task</code> class
function TaskClass.new(title: string?)
local self = setmetatable({}, TaskClass)
self.Title = title or "Task"
self.IsActive = false
self.IsCompleted = false
self.IsCancelled = false
self.Players = {}
self.Participants = {} -- Used for multi-player tasks for checking who has actually contributed something to earn rewards.
self.Objectives = {}
self.Events = {
Activated = Signal.new(),
Completed = Signal.new(),
Cancelled = Signal.new(),
}
return self
end
-- Returns whether the task is currently active
function TaskClass:IsActive(): boolean
return self.IsActive
end
-- Returns whether the task is completed
function TaskClass:IsCompleted(): boolean
return self.IsCompleted
end
-- Returns a table of all players assigned to the task
function TaskClass:GetPlayers(): {Player}
return self.Players
end
-- Returns a table of all objectives of this task
function TaskClass:GetObjectives(): {}
return self.Objectives
end
-- Adds a player/players to the table of players assigned to the task
function TaskClass:AddPlayers(players: Player | {Player}): {Player}
if typeof(players) == "Player" then
table.insert(self.Players, players)
elseif typeof(players) == "table" then
for _, player in pairs(players) do
table.insert(self.Players, player)
end
end
if #self:GetPlayers() < 1 then self:Destroy() end
return self.Players
end
-- Removes a player/players from the table of players assigned to the task
function TaskClass:RemovePlayers(players: Player | {Player}): {Player}
if typeof(players) == "Player" then
for index, player in pairs(self.Players) do
if player == players then
table.remove(self.Players, index)
end
end
elseif typeof(players) == "table" then
for _, player in pairs(players) do
for index, _player in pairs(self.Players) do
if player == _player then
table.remove(self.Players, index)
end
end
end
end
return self.Players
end
function TaskClass:AddObjective(objective: {Parent: {any}, IsActive: boolean, IsCompleted: boolean})
objective.Parent = self
table.insert(self.Objectives, objective)
end
--[[
Activates the task and all of its assigned objectives
It is recommended to activate AFTER all objectives have been added to the task
]]
function TaskClass:Activate()
if self:IsActive() then error("This task is already active") end
self.IsActive = true
self.Events.Activated:Fire()
end
--[[
Bypasses the objective completion check and completes the task, used by objective class to complete the objective
This should only be used in extreme cases where the objective completion check is not working correctly
]]
function TaskClass:Complete(bypassRequirements: boolean?)
if self:IsCompleted() then error("This task is already completed") end
if not bypassRequirements and not self:CanComplete() then warn("This task has remaining objectives to complete") end
self.IsCompleted = true
self.Events.Completed:Fire()
end
--[[
Used by `ObjectiveClass` to determine if it should call `:Complete()`.
Returns `true` if all objectives are completed and the task is not cancelled.
Returns `false` otherwise
]]
function TaskClass:CanComplete(): boolean
if self.IsCancelled then return false end
for _, objective in pairs(self:GetObjectives()) do
if not objective:IsCompleted() then
return false
end
end
return true
end
--[[
Cancels the task making it impossible to complete
Used for fail states or cancelling a task early, such as if a player dies while doing a task
]]
function TaskClass:Cancel()
if self:IsCompleted() then error("This task is already completed") end
self.IsCancelled = true
self.Events.Cancelled:Fire()
end
function TaskClass:Destroy()
for _, objective in pairs(self:GetObjectives()) do
objective:Destroy()
end
for _, event: RBXScriptConnection in pairs(self.Events) do
event:Disconnect()
end
self = nil
end
return TaskClass
Objective
-- ObjectiveClass.lua
-- Galicate @ 12:00 12/11/2024
local ObjectiveClass = {}
ObjectiveClass.__index = ObjectiveClass
-- Returns a new <code>Objective</code> class
function ObjectiveClass.new()
local self = setmetatable({}, ObjectiveClass)
self.IsActive = false
self.IsCompleted = false
return self
end
-- Returns whether the objective is currently active
function ObjectiveClass:IsActive(): boolean
return self.IsActive
end
-- Returns whether the objective is completed
function ObjectiveClass:IsCompleted(): boolean
return self.IsCompleted
end
function ObjectiveClass:Activate()
if self:IsActive() then error("This objective is already active") end
self.IsActive = true
end
function ObjectiveClass:Complete()
if self:IsCompleted() then error("This objective is already active") end
self.IsCompleted = true
if self.Task:CanComplete() then
self.Task:Complete()
end
end
function ObjectiveClass:Destroy()
self = nil
end
return ObjectiveClass