Neg_Utils/Achievments/AchievmentManager.cs

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);
}
}
}
}