Hey everyone and @Brian_Trotter , hoping you can help me solve a bug!
I’ve implemented the Track Kills for Quests feature, and have it working (have added a collectibles extension of it too). The trouble is, it works perfectly within the scene the quest is received only.
If I transition to another scene, I have the quest list and objectives still, but the ones that use the AchievementCounter automatically complete, even if their requirements have not been met. Has anyone else run into this issue?
I’ll post what I think are the relevant scripts here to make sure I’m not missing anything:
AchievementCounter.cs
public class AchievementCounter : MonoBehaviour, IPredicateEvaluator, IJsonSaveable
{
private Dictionary<string, int> counts = new Dictionary<string, int>();
public event System.Action onCountChanged;
public int AddToCount(string token, int amount, bool onlyIfExists = false)
{
if (!counts.ContainsKey(token))
{
if (onlyIfExists) return 0;
counts[token] = amount;
onCountChanged?.Invoke();
return amount;
}
counts[token] += amount;
onCountChanged?.Invoke();
return counts[token];
}
public int RegisterCounter(string token, bool overwrite = false)
{
if (!counts.ContainsKey(token) || overwrite)
{
counts[token] = 0;
onCountChanged?.Invoke();
}
return counts[token];
}
public int GetCounterValue(string token)
{
if (!counts.ContainsKey(token)) return 0;
return counts[token];
}
public bool? Evaluate(EPredicate predicate, string[] parameters)
{
if (predicate == EPredicate.HasKilled)
{
if (int.TryParse(parameters[1], out int intParameter))
{
RegisterCounter(parameters[0]);
return counts[parameters[0]] >= intParameter;
}
return false;
}
else if (predicate == EPredicate.HasCollected)
{
if (int.TryParse(parameters[1], out int intParameter))
{
RegisterCounter(parameters[0]);
return counts[parameters[0]] >= intParameter;
}
return false;
}
return null;
}
public JToken CaptureAsJToken()
{
JObject state = new JObject();
IDictionary<string, JToken> stateDict = state;
foreach (KeyValuePair<string, int> keyValuePair in counts)
{
stateDict[keyValuePair.Key] = JToken.FromObject(keyValuePair.Value);
}
return state;
}
public void RestoreFromJToken(JToken state)
{
if (state is JObject stateObject)
{
IDictionary<string, JToken> stateDict = stateObject;
counts.Clear();
foreach (KeyValuePair<string, JToken> keyValuePair in stateDict)
{
counts[keyValuePair.Key] = keyValuePair.Value.ToObject<int>();
}
onCountChanged?.Invoke();
}
}
}
DeathCounter.cs
public class DeathCounter : MonoBehaviour
{
[SerializeField] private string identifier;
[SerializeField] private bool onlyIfInitialized = true;
private AchievementCounter counter;
private void Awake()
{
counter = GameObject.FindWithTag("Player").GetComponent<AchievementCounter>();
GetComponent<Health>().OnDie += AddToCount;
}
private void AddToCount()
{
counter.AddToCount(identifier, 1, onlyIfInitialized);
}
}
QuestList.cs:
public class QuestList : MonoBehaviour, IPredicateEvaluator, IJsonSaveable
{
List<QuestStatus> statuses = new List<QuestStatus>();
public event Action onQuestListUpdated;
private void Update()
{
CompleteObjectivesByPredicates();
}
public void AddQuest(Quest quest)
{
if (HasQuest(quest)) return;
QuestStatus newStatus = new QuestStatus(quest);
statuses.Add(newStatus);
if (onQuestListUpdated != null)
{
onQuestListUpdated();
}
}
public void CompleteObjective(Quest quest, string objective)
{
QuestStatus status = GetQuestStatus(quest);
status.CompleteObjective(objective);
if (status.IsComplete())
{
GiveReward(quest);
}
if (onQuestListUpdated != null)
{
onQuestListUpdated();
}
}
public bool HasQuest(Quest quest)
{
return GetQuestStatus(quest) != null;
}
public IEnumerable<QuestStatus> GetStatuses()
{
return statuses;
}
private QuestStatus GetQuestStatus(Quest quest)
{
foreach (QuestStatus status in statuses)
{
if (status.GetQuest() == quest)
{
return status;
}
}
return null;
}
private void GiveReward(Quest quest)
{
foreach (var reward in quest.GetRewards())
{
bool success = GetComponent<Inventory>().AddToFirstEmptySlot(reward.item, reward.number);
if (!success)
{
GetComponent<ItemDropper>().DropItem(reward.item, reward.number);
}
}
}
private void CompleteObjectivesByPredicates()
{
foreach (QuestStatus status in statuses)
{
if (status.IsComplete()) continue;
Quest quest = status.GetQuest();
foreach (var objective in quest.GetObjectives())
{
if (status.IsObjectiveComplete(objective.reference)) continue;
if (!objective.useCondition) continue;
if (objective.completionCondition.Check(GetComponents<IPredicateEvaluator>()))
{
CompleteObjective(quest, objective.reference);
}
}
}
}
public JToken CaptureAsJToken()
{
JArray state = new JArray();
IList<JToken> stateList = state;
foreach (QuestStatus status in statuses)
{
stateList.Add(status.CaptureAsJToken());
}
return state;
}
public void RestoreFromJToken(JToken state)
{
if (state is JArray stateArray)
{
statuses.Clear();
IList<JToken> stateList = stateArray;
foreach (JToken token in stateList)
{
statuses.Add(new QuestStatus(token));
}
onQuestListUpdated?.Invoke();
}
}
public bool? Evaluate(EPredicate predicate, string[] parameters)
{
switch (predicate)
{
case EPredicate.HasQuest:
return HasQuest(Quest.GetByName(parameters[0]));
case EPredicate.CompletedQuest:
QuestStatus status = GetQuestStatus(Quest.GetByName(parameters[0]));
if (status == null) return false;
return status.IsComplete();
case EPredicate.CompletedObjective:
QuestStatus testStatus = GetQuestStatus(Quest.GetByName(parameters[0]));
if (testStatus == null) return false;
return testStatus.IsObjectiveComplete(parameters[1]);
}
return null;
}
}
QuestStatus.cs
public class QuestStatus
{
Quest quest;
List<string> completedObjectives = new List<string>();
[System.Serializable]
class QuestStatusRecord
{
public string questName;
public List<string> completedObjectives;
}
public QuestStatus(Quest quest)
{
this.quest = quest;
}
public QuestStatus(object objectState)
{
QuestStatusRecord state = objectState as QuestStatusRecord;
quest = Quest.GetByName(state.questName);
completedObjectives = state.completedObjectives;
}
public Quest GetQuest()
{
return quest;
}
public bool IsComplete()
{
foreach (var objective in quest.GetObjectives())
{
if (!completedObjectives.Contains(objective.reference))
{
return false;
}
}
return true;
}
public int GetCompletedCount()
{
return completedObjectives.Count;
}
public bool IsObjectiveComplete(string objective)
{
return completedObjectives.Contains(objective);
}
public void CompleteObjective(string objective)
{
if (quest.HasObjective(objective))
{
completedObjectives.Add(objective);
}
}
public object CaptureState()
{
QuestStatusRecord state = new QuestStatusRecord();
state.questName = quest.name;
state.completedObjectives = completedObjectives;
return state;
}
public QuestStatus(JToken objectState)
{
if (objectState is JObject state)
{
IDictionary<string, JToken> stateDict = state;
quest = Quest.GetByName(stateDict["questName"].ToObject<string>());
completedObjectives.Clear();
if (stateDict["completedObjectives"] is JArray completedState)
{
IList<JToken> completedStateArray = completedState;
foreach (JToken objective in completedStateArray)
{
completedObjectives.Add(objective.ToObject<string>());
}
}
}
}
public JToken CaptureAsJToken()
{
JObject state = new JObject();
IDictionary<string, JToken> stateDict = state;
stateDict["questName"] = quest.name;
JArray completedState = new JArray();
IList<JToken> completedStateArray = completedState;
foreach (string objective in completedObjectives)
{
completedStateArray.Add(JToken.FromObject(objective));
}
stateDict["completedObjectives"] = completedState;
return state;
}
}