341 lines
14 KiB
C#
341 lines
14 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.AddressableAssets;
|
|
using System.Runtime.CompilerServices;
|
|
using UnityEditor;
|
|
|
|
namespace NEG.Utils.Achievments
|
|
{
|
|
using AchievmentTypes;
|
|
|
|
public class AchievmentManager
|
|
{
|
|
public class Builder
|
|
{
|
|
public const string DefaultAchivmentsConfigLabel = "Achivments";
|
|
|
|
private AchievmentManager manager = new AchievmentManager();
|
|
private IAchivmentStorage storage;
|
|
|
|
public static Builder FromDefaultConfig()
|
|
{
|
|
return FromLabeledConfig(DefaultAchivmentsConfigLabel);
|
|
}
|
|
|
|
public static Builder FromLabeledConfig(string label)
|
|
{
|
|
var builder = new Builder();
|
|
|
|
var handle = Addressables.LoadAssetsAsync<IAchivmentManagerConfig>((IEnumerable)new string[] { label }, delegate { }, Addressables.MergeMode.Union, false);
|
|
|
|
var configs = handle.WaitForCompletion();
|
|
|
|
foreach (var config in configs)
|
|
{
|
|
config.Apply(builder);
|
|
}
|
|
|
|
foreach (var config in configs)
|
|
{
|
|
config.ApplyLast(builder);
|
|
}
|
|
|
|
Addressables.Release(handle);
|
|
|
|
return builder;
|
|
}
|
|
|
|
public Builder WithLabeledDefinitions(string label)
|
|
{
|
|
var handle = Addressables.LoadAssetsAsync<AchivmentDefinitionCollection>((IEnumerable)new string[] { label }, delegate { }, Addressables.MergeMode.Union, false);
|
|
|
|
var achivmentCollections = handle.WaitForCompletion();
|
|
|
|
foreach (var achivmentCollection in achivmentCollections)
|
|
WithDefinitionsFrom(achivmentCollection);
|
|
|
|
return this;
|
|
}
|
|
|
|
public Builder WithDefinitionsFrom(AchivmentDefinitionCollection collection)
|
|
{
|
|
foreach (var def in collection.Achivments)
|
|
{
|
|
manager.RegisterAchivment(def);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
public Builder LoadedFrom(IAchivmentStorage storageIn)
|
|
{
|
|
if (storage != null)
|
|
{
|
|
throw new ApplicationException("Cannot Load achivment data from multiple storage instances");
|
|
}
|
|
|
|
this.storage = storageIn;
|
|
|
|
return this;
|
|
}
|
|
|
|
public Builder WithCallbackReciever(IAchievmentCallbackReciever callbackReciever)
|
|
{
|
|
manager.AddCallbackReciever(callbackReciever);
|
|
return this;
|
|
}
|
|
|
|
public AchievmentManager Build()
|
|
{
|
|
//TODO
|
|
/*if (storage != null)
|
|
{
|
|
manager.LoadFrom(storage);
|
|
}*/
|
|
return manager;
|
|
}
|
|
}
|
|
|
|
public delegate void AchievmentCompletedCallback(AchievmentData achivment);
|
|
public delegate void AchievmentStateChangedCallback(AchievmentData achivment);
|
|
|
|
public event AchievmentCompletedCallback AchievmentCompleted;
|
|
public event AchievmentStateChangedCallback AchievmentStateChanged;
|
|
|
|
private Dictionary<string, AchievmentDefinition> definitionCache;
|
|
private Dictionary<AchievmentDefinition, AchievmentData> dataCache;
|
|
|
|
private AchievmentManager()
|
|
{
|
|
definitionCache = new Dictionary<string, AchievmentDefinition>();
|
|
dataCache = new Dictionary<AchievmentDefinition, AchievmentData>();
|
|
}
|
|
|
|
private void RegisterAchivment(AchievmentDefinition definition)
|
|
{
|
|
if (!definitionCache.ContainsKey(definition.Id))
|
|
{
|
|
definitionCache.Add(definition.Id, definition);
|
|
dataCache.Add(definition, definition.Construct());
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"Duplicate Achivment with ID: {definition.Id}");
|
|
}
|
|
}
|
|
|
|
/*private void LoadFrom(IAchivmentStorage achivmentStorage)
|
|
{
|
|
foreach (var entry in definitionCache)
|
|
{
|
|
var storedProgress = achivmentStorage.GetStoredAchivmentProgress(entry.Key);
|
|
|
|
dataCache[entry.Value].ProgressLeft = storedProgress;
|
|
}
|
|
}*/
|
|
|
|
public void AddCallbackRecievers(IEnumerable<IAchievmentCallbackReciever> initialCallbacks)
|
|
{
|
|
foreach (var callback in initialCallbacks)
|
|
{
|
|
AddCallbackReciever(callback);
|
|
}
|
|
}
|
|
|
|
public void AddCallbackReciever(IAchievmentCallbackReciever callback)
|
|
{
|
|
AchievmentCompleted += callback.AchievmentCompleted;
|
|
AchievmentStateChanged += callback.AchievmentStateChanged;
|
|
}
|
|
|
|
public void RemoveCallbackReciever(IAchievmentCallbackReciever callback)
|
|
{
|
|
AchievmentCompleted -= callback.AchievmentCompleted;
|
|
AchievmentStateChanged -= callback.AchievmentStateChanged;
|
|
}
|
|
|
|
#region Achievment Manipulation (Sets, Gets)
|
|
|
|
/// <summary>
|
|
/// Returns if an achivment at a given id is completed
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <returns></returns>
|
|
public bool IsCompleted(string id)
|
|
{
|
|
return GetAchievmentForId(id).IsCompleted;
|
|
}
|
|
|
|
#region Toggle
|
|
/// <summary>
|
|
/// Sets a <see cref="ToggleAchievmentData"/> as completed, after which sends <see cref="AchievmentStateChanged"/>, also if the achievment is completed sends a <see cref="AchievmentCompleted"/>. <br/>
|
|
/// If there is no achievment at a given id throws an <see cref="ApplicationException"/>. If achievment at a given id is of a wrong tupe throws an <see cref="ApplicationException"/>
|
|
/// </summary>
|
|
/// <seealso cref="ToggleAchievmentDefinition"/>
|
|
public void SetToggleAchivment(string id)
|
|
{
|
|
ManipulateAchievment<ToggleAchievmentData>(id, (achievment) => achievment.CompletionState = true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a completion state from a <see cref="ToggleAchievmentData"/>.<br/>
|
|
/// If there is no achievment at a given id throws an <see cref="ApplicationException"/>. If achievment at a given id is of a wrong tupe throws an <see cref="ApplicationException"/>
|
|
/// </summary>
|
|
/// <seealso cref="ToggleAchievmentDefinition"/>
|
|
public bool GetToggleState(string id)
|
|
{
|
|
return GetAchievmentForId<ToggleAchievmentData>(id).CompletionState;
|
|
}
|
|
#endregion
|
|
|
|
#region Int
|
|
/// <summary>
|
|
/// Sets progress of a given <see cref="IntAchievmentData"/> to <paramref name="progress"/>, after which sends <see cref="AchievmentStateChanged"/>, also if the achievment is completed sends a <see cref="AchievmentCompleted"/>. <br/>
|
|
/// If there is no achievment at a given id throws an <see cref="ApplicationException"/>. If achievment at a given id is of a wrong tupe throws an <see cref="ApplicationException"/>.
|
|
/// </summary>
|
|
/// <seealso cref="IntAchievmentDefinition"/>
|
|
public void SetIntProgress(string id, int progress)
|
|
{
|
|
ManipulateAchievment<IntAchievmentData>(id, (achievment) => achievment.CurrentProgress = progress);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes progress of a given <see cref="IntAchievmentData"/> by <paramref name="delta"/>, after which sends <see cref="AchievmentStateChanged"/>, also if the achievment is completed sends a <see cref="AchievmentCompleted"/>. <br/>
|
|
/// If there is no achievment at a given id throws an <see cref="ApplicationException"/>. If achievment at a given id is of a wrong tupe throws an <see cref="ApplicationException"/>
|
|
/// </summary>
|
|
/// <seealso cref="IntAchievmentDefinition"/>
|
|
public void CangeIntProgress(string id, int delta)
|
|
{
|
|
ManipulateAchievment<IntAchievmentData>(id, (achievment) => achievment.CurrentProgress += delta);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets current progress from a <see cref="IntAchievmentData"/>.<br/>
|
|
/// If there is no achievment at a given id throws an <see cref="ApplicationException"/>. If achievment at a given id is of a wrong tupe throws an <see cref="ApplicationException"/>
|
|
/// </summary>
|
|
/// <seealso cref="ToggleAchievmentDefinition"/>
|
|
public int GetIntProgress(string id)
|
|
{
|
|
return GetAchievmentForId<IntAchievmentData>(id).CurrentProgress;
|
|
}
|
|
#endregion
|
|
|
|
#region Float
|
|
/// <summary>
|
|
/// Sets progress of a given <see cref="FloatAchievmentData"/> to <paramref name="progress"/>, after which sends <see cref="AchievmentStateChanged"/>, also if the achievment is completed sends a <see cref="AchievmentCompleted"/>. <br/>
|
|
/// If there is no achievment at a given id throws an <see cref="ApplicationException"/>. If achievment at a given id is of a wrong tupe throws an <see cref="ApplicationException"/>
|
|
/// </summary>
|
|
/// <seealso cref="FloatAchievmentDefinition"/>
|
|
public void SetFloatProgress(string id, float progress)
|
|
{
|
|
ManipulateAchievment<FloatAchievmentData>(id, (achievment) => achievment.CurrentProgress = progress);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes progress of a given <see cref="FloatAchievmentData"/> by <paramref name="delta"/>, after which sends <see cref="AchievmentStateChanged"/>, also if the achievment is completed sends a <see cref="AchievmentCompleted"/>. <br/>
|
|
/// If there is no achievment at a given id throws an <see cref="ApplicationException"/>. If achievment at a given id is of a wrong tupe throws an <see cref="ApplicationException"/>
|
|
/// </summary>
|
|
/// <seealso cref="FloatAchievmentDefinition"/>
|
|
public void CangeFloatProgress(string id, float delta)
|
|
{
|
|
ManipulateAchievment<FloatAchievmentData>(id, (achievment) => achievment.CurrentProgress += delta);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets current progress from a <see cref="FloatAchievmentData"/>.<br/>
|
|
/// If there is no achievment at a given id throws an <see cref="ApplicationException"/>. If achievment at a given id is of a wrong tupe throws an <see cref="ApplicationException"/>
|
|
/// </summary>
|
|
/// <seealso cref="FloatAchievmentDefinition"/>
|
|
public float GetFloatProgress(string id)
|
|
{
|
|
return GetAchievmentForId<FloatAchievmentData>(id).CurrentProgress;
|
|
}
|
|
#endregion
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Returns an achievment of type <typeparamref name="T"/> under <paramref name="id"/>
|
|
/// </summary>
|
|
/// <typeparam name="T">Type of the achievment</typeparam>
|
|
/// <param name="id">Id of requested achievment</param>
|
|
/// <remarks>throws an <see cref="ApplicationException"/> if either there is no achievment under id or achievment under id is of a different type</remarks>
|
|
public T GetAchievmentForId<T>(string id) where T : AchievmentData
|
|
{
|
|
return CheckAchievmentType<T>(GetAchievmentForId(id));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an achievment under <paramref name="id"/>
|
|
/// </summary>
|
|
/// <param name="id">Id of requested achievment</param>
|
|
/// <remarks>throws an <see cref="ApplicationException"/> if there is no achievment under id</remarks>
|
|
public AchievmentData GetAchievmentForId(string id)
|
|
{
|
|
var def = definitionCache.GetValueOrDefault(id);
|
|
if (def != null)
|
|
{
|
|
return dataCache[def];
|
|
}
|
|
else
|
|
{
|
|
throw new ApplicationException($"Invalid achivment id {id}");
|
|
}
|
|
}
|
|
|
|
private T CheckAchievmentType<T>(AchievmentData data) where T : AchievmentData
|
|
{
|
|
if (data is not T convetred)
|
|
{
|
|
throw new ApplicationException($"Attempting to perform an operation on an invalid achievment type. Expected {typeof(T)} got {data.GetType()}");
|
|
}
|
|
return convetred;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="id"></param>
|
|
/// <param name="manipulation">Action to perform on the achievment</param>
|
|
private void ManipulateAchievment<T>(string id, Action<T> manipulation) where T : AchievmentData
|
|
{
|
|
var data = GetAchievmentForId<T>(id);
|
|
|
|
if (CheckNotCompleted(data))
|
|
{
|
|
return;
|
|
}
|
|
|
|
manipulation(data);
|
|
|
|
SendUpdateCallbacks(data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to print a warning if an achievment is already completed
|
|
/// </summary>
|
|
/// <param name="data"></param>
|
|
/// <returns>Completion state</returns>
|
|
private bool CheckNotCompleted(AchievmentData data)
|
|
{
|
|
if (data.IsCompleted)
|
|
{
|
|
Debug.LogWarning($"Achievment already completed: {data.Achivment.Id}");
|
|
}
|
|
return data.IsCompleted;
|
|
}
|
|
|
|
private void SendUpdateCallbacks(AchievmentData data)
|
|
{
|
|
AchievmentStateChanged?.Invoke(data);
|
|
|
|
if (data.IsCompleted)
|
|
{
|
|
AchievmentCompleted?.Invoke(data);
|
|
}
|
|
}
|
|
}
|
|
} |