Skip to content

Commit

Permalink
feat(quest events): quest collection now has event hooks
Browse files Browse the repository at this point in the history
You can subscribe to add, complete, update, and task complete events. Extremely useful for post
processing tasks and quests with extra meta data
  • Loading branch information
ashblue committed Mar 26, 2024
1 parent 359ba7b commit c535109
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,67 @@
using System.Collections.Generic;
using System.Linq;
using CleverCrow.Fluid.QuestJournals.Tasks;
using CleverCrow.Fluid.QuestJournals.Utilities;
using UnityEngine;

namespace CleverCrow.Fluid.QuestJournals.Quests {
public class QuestCollection : IQuestCollection {
private readonly Dictionary<IQuestDefinition, IQuestInstance> _quests = new Dictionary<IQuestDefinition, IQuestInstance>();
private readonly UnityEventSafe<IQuestInstance> _eventQuestAdd = new();
private readonly UnityEventSafe<IQuestInstance> _eventQuestComplete = new();
private readonly UnityEventSafe<IQuestInstance> _eventQuestUpdate = new();
private readonly UnityEventSafe<IQuestInstance, ITaskInstanceReadOnly> _eventQuestTaskComplete = new();

private readonly Dictionary<IQuestDefinition, IQuestInstance> _quests = new();
private readonly IQuestDatabase _questDatabase;

public QuestCollection (IQuestDatabase questDatabase) {
questDatabase.Setup();
_questDatabase = questDatabase;
}

/// <summary>
/// Triggers when a quest is added to the collection. Generally useful for UI updates
/// </summary>
public IUnityEventReadOnly<IQuestInstance> EventQuestAdd => _eventQuestAdd;

/// <summary>
/// Triggers when a quest is completed due to running out of tasks. Useful for quest completion post processing events
/// </summary>
public IUnityEventReadOnly<IQuestInstance> EventQuestComplete => _eventQuestComplete;

/// <summary>
/// Triggered when a quest has a task change. A good place to update your UI if you are displaying quest progress
/// </summary>
public IUnityEventReadOnly<IQuestInstance> EventQuestUpdate => _eventQuestUpdate;

/// <summary>
/// Triggers whenever a task is completed with the corresponding quest and task instance. Useful to fire post processing events with completed tasks.
/// </summary>
public IUnityEventReadOnly<IQuestInstance, ITaskInstanceReadOnly> EventQuestTaskComplete => _eventQuestTaskComplete;

public IQuestInstance Add (IQuestDefinition definition) {
var instance = AddInternal(definition);

Bind(instance);
_eventQuestAdd.Invoke(instance);

return instance;
}

/// <summary>
/// Primarily a debugging method. Will not trigger events in the way you might expect. Not production recommended (use Add(IQuestDefinition) instead).
/// </summary>
public IQuestInstance Add (ITaskDefinition definition) {
var quest = AddInternal(definition.Parent);
quest.SetTask(definition);
Bind(quest);

_eventQuestAdd.Invoke(quest);

return quest;
}

IQuestInstance AddInternal (IQuestDefinition definition) {
var existingResult = Get(definition);
if (existingResult != null) {
return existingResult;
Expand All @@ -25,11 +73,16 @@ public IQuestInstance Add (IQuestDefinition definition) {
return instance;
}

public IQuestInstance Add (ITaskDefinition definition) {
var quest = Add(definition.Parent);
quest.SetTask(definition);
void Bind (IQuestInstance instance) {
instance.EventComplete.AddListener(_eventQuestComplete.Invoke);
instance.EventUpdate.AddListener(_eventQuestUpdate.Invoke);
instance.EventTaskComplete.AddListener(_eventQuestTaskComplete.Invoke);
}

return quest;
void Unbind (IQuestInstance instance) {
instance.EventComplete.RemoveListener(_eventQuestComplete.Invoke);
instance.EventUpdate.RemoveListener(_eventQuestUpdate.Invoke);
instance.EventTaskComplete.RemoveListener(_eventQuestTaskComplete.Invoke);
}

public IQuestInstance Get (IQuestDefinition definition) {
Expand Down Expand Up @@ -59,6 +112,11 @@ public string Save () {
}

public void Load (string save) {
// Unbind all events just in case
foreach (var quest in _quests.Values) {
Unbind(quest);
}

_quests.Clear();

var data = JsonUtility.FromJson<QuestCollectionSave>(save);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using CleverCrow.Fluid.QuestJournals.Tasks;
using CleverCrow.Fluid.QuestJournals.Utilities;

namespace CleverCrow.Fluid.QuestJournals.Quests {
public interface IQuestInstance {
Expand All @@ -9,7 +10,11 @@ public interface IQuestInstance {
QuestStatus Status { get; }

IReadOnlyList<ITaskInstanceReadOnly> Tasks { get; }
ITaskInstance ActiveTask { get; }
ITaskInstanceReadOnly ActiveTask { get; }

IUnityEventReadOnly<IQuestInstance> EventComplete { get; }
IUnityEventReadOnly<IQuestInstance> EventUpdate { get; }
IUnityEventReadOnly<IQuestInstance, ITaskInstanceReadOnly> EventTaskComplete { get; }

void Next ();
void SetTask (ITaskDefinition task);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
using System.Collections.Generic;
using System.Linq;
using CleverCrow.Fluid.QuestJournals.Tasks;
using CleverCrow.Fluid.QuestJournals.Utilities;
using UnityEngine;

namespace CleverCrow.Fluid.QuestJournals.Quests {
public class QuestInstance : IQuestInstance {
private readonly List<ITaskInstance> _tasks = new List<ITaskInstance>();
private readonly List<ITaskInstance> _tasks = new();

readonly IUnityEventSafe<IQuestInstance> _eventComplete = new UnityEventSafe<IQuestInstance>();
readonly IUnityEventSafe<IQuestInstance> _eventUpdate = new UnityEventSafe<IQuestInstance>();
readonly IUnityEventSafe<IQuestInstance, ITaskInstanceReadOnly> _eventTaskComplete = new UnityEventSafe<IQuestInstance, ITaskInstanceReadOnly>();

private int _taskIndex;

public IQuestDefinition Definition { get; }
Expand All @@ -14,24 +20,33 @@ public class QuestInstance : IQuestInstance {
public IReadOnlyList<ITaskInstanceReadOnly> Tasks => _tasks;
public QuestStatus Status => _taskIndex >= _tasks.Count ? QuestStatus.Complete : QuestStatus.Ongoing;

public ITaskInstance ActiveTask {
public IUnityEventReadOnly<IQuestInstance> EventComplete => _eventComplete;
public IUnityEventReadOnly<IQuestInstance> EventUpdate => _eventUpdate;
public IUnityEventReadOnly<IQuestInstance, ITaskInstanceReadOnly> EventTaskComplete => _eventTaskComplete;

public ITaskInstanceReadOnly ActiveTask {
get {
if (_tasks.Count == 0) return null;
return Status == QuestStatus.Complete ? _tasks[_tasks.Count - 1] : _tasks[_taskIndex];
}
}

ITaskInstance ActiveTaskInternal => ActiveTask as ITaskInstance;

public QuestInstance (IQuestDefinition definition) {
Definition = definition;
PopulateTasks(definition.Tasks);
}

/// <summary>
/// Primarily a debugging method. Not recommended in production. Call Next() instead
/// </summary>
public void SetTask (ITaskDefinition task) {
_taskIndex = _tasks.FindIndex((t) => t.Definition == task);

for (var i = 0; i < Tasks.Count; i++) {
if (_taskIndex == i) {
ActiveTask.Begin();
ActiveTaskInternal.Begin();
continue;
}

Expand All @@ -42,17 +57,26 @@ public void SetTask (ITaskDefinition task) {

_tasks[i].ClearStatus();
}

_eventUpdate.Invoke(this);
}

public void Next () {
if (Status == QuestStatus.Complete) return;

var prev = ActiveTask;
ActiveTask.Complete();
ActiveTaskInternal.Complete();
_taskIndex += 1;

if (prev != ActiveTask) {
ActiveTask?.Begin();
ActiveTaskInternal?.Begin();
}

_eventTaskComplete.Invoke(this, prev);
_eventUpdate.Invoke(this);

if (Status == QuestStatus.Complete) {
_eventComplete.Invoke(this);
}
}

Expand All @@ -77,7 +101,12 @@ public void Load (string save) {
});
}

/// <summary>
/// Primarily a debugging method. Call Next() instead
/// </summary>
public void Complete () {
if (Status == QuestStatus.Complete) return;

while (Status != QuestStatus.Complete) {
Next();
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using UnityEngine.Events;

namespace CleverCrow.Fluid.QuestJournals.Utilities {
public interface IUnityEventReadOnly {
void AddListener (UnityAction call);
void RemoveListener (UnityAction call);
}

public interface IUnityEventReadOnly<T> {
void AddListener (UnityAction<T> call);
void RemoveListener (UnityAction<T> call);
}

public interface IUnityEventReadOnly<T1, T2> {
void AddListener (UnityAction<T1, T2> call);
void RemoveListener (UnityAction<T1, T2> call);
}

public interface IUnityEventReadOnly<T1, T2, T3> {
void AddListener (UnityAction<T1, T2, T3> call);
void RemoveListener (UnityAction<T1, T2, T3> call);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace CleverCrow.Fluid.QuestJournals.Utilities {
public interface IUnityEventSafe : IUnityEventReadOnly {
void Invoke ();
}

public interface IUnityEventSafe<T> : IUnityEventReadOnly<T> {
void Invoke (T arg);
}

public interface IUnityEventSafe<T1, T2> : IUnityEventReadOnly<T1, T2> {
void Invoke (T1 arg1, T2 arg2);
}

public interface IUnityEventSafe<T1, T2, T3> : IUnityEventReadOnly<T1, T2, T3> {
void Invoke (T1 arg1, T2 arg2, T3 arg3);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using UnityEngine.Events;

namespace CleverCrow.Fluid.QuestJournals.Utilities {
/// <summary>
/// Unity events designed for external public class safety. Only allows for basic subscribe and unsubscribe behaviors via IUnityEventReadOnly
/// </summary>
[System.Serializable]
public class UnityEventSafe : UnityEvent, IUnityEventSafe {
}

public class UnityEventSafe<T1> : UnityEvent<T1>, IUnityEventSafe<T1> {
}

public class UnityEventSafe<T1, T2> : UnityEvent<T1, T2>, IUnityEventSafe<T1, T2> {
}

public class UnityEventSafe<T1, T2, T3> : UnityEvent<T1, T2, T3>, IUnityEventSafe<T1, T2, T3> {
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c535109

Please sign in to comment.