Story/ObjectiveSystem.cs
using Sandbox.UI;

namespace Opium;

public enum ObjectiveState
{
	NotStarted,
	InProgress,
	Finished
}

public class Objective
{
	[KeyProperty] public string Id { get; set; }
	[KeyProperty] public string Name { get; set; }
	public ObjectiveState State { get; set; }

	public Objective()
	{

	}

	public Objective( string id, string name )
	{
		Id = id;
		Name = name;
	}

	public string GetStateColor()
	{
		return State switch
		{
			ObjectiveState.InProgress => "orange",
			ObjectiveState.Finished => new Color32( 35, 200, 25, 25 ).Hex,
			_ => new Color32( 200, 15, 25, 25 ).Hex
		};
	}
}

/// <summary>
/// Super simple crude objectives.
/// </summary>
public partial class ObjectiveSystem : GameObjectSystem
{
	public ObjectiveSystem( Scene scene ) : base( scene )
	{
		Objectives.Clear();
	}

	private static List<Objective> Objectives { get; set; } = new();

	public static IReadOnlyList<Objective> GetObjectives()
	{
		return Objectives.AsReadOnly();
	}

	[ActionGraphNode( "opium.story.objectives.add" ), Category( "Opium" ), Title( "Add Objective" )]
	public static void Add( string id, string text )
	{
		Objectives.Add( new Objective( id, text ) );
	}

	public static void Add( Objective obj )
	{
		Add( obj.Id, obj.Name );
	}

	[ActionGraphNode( "opium.story.objectives.finish" ), Category( "Opium" ), Title( "Finish Objective" )]
	public static void Finish( string id )
	{
		var obj = Objectives.FirstOrDefault( x => x.Id.Equals( id ) );

		if ( obj is not null )
		{
			Log.Info( $"Found an objective to finish! {obj}" );
			obj.State = ObjectiveState.Finished;

			if ( PlayerController.Local.IsValid() )
			{
				PlayerController.Local.Components.Get<PlayerObjectivesUI>( FindMode.EnabledInSelfAndDescendants )?.ShowObjectiveUI();
			}
		}
	}

	[ActionGraphNode( "opium.story.objectives.delete" ), Category( "Opium" ), Title( "Delet Objective" )]
	public static void Delete( string id )
	{
		Objectives.Remove( Objectives.FirstOrDefault( x => x.Id == id ) );
	}
}