achivments #1

Closed
Ghost wants to merge 13 commits from achivments into main
36 changed files with 642 additions and 286 deletions
Showing only changes of commit 1bd77d9628 - Show all commits

View File

@ -0,0 +1,341 @@
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);
Review

Why need this, when all are empty

Why need this, when all are empty
Review

was needed in earlier version, i will remove it

was needed in earlier version, i will remove it
}
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;
}
Review

no check for empty label

no check for empty label
Review

empty meaning null or empty meaning label with nothing assigned?

empty meaning null or empty meaning label with nothing assigned?
Review

you check for nothing, why even bother questioning

you check for nothing, why even bother questioning
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);
}
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 841f326ebfd59e646835bf81432d3ae4 guid: 7339f725e382e4b4bab7db6d7cc14b30
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 48ec3460d89c2f144827761002d7f4d9 guid: 79e626bf8b94e5f4d813912f9b1d304e
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -0,0 +1,21 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine;
namespace NEG.Utils.Achievments.AchievmentTypes
{
public abstract class AchievmentData
{
public AchievmentDefinition Achivment { get; private set; }
public abstract bool IsCompleted { get; }
public AchievmentData(AchievmentDefinition achivment)
{
Achivment = achivment;
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: fad96d35cce2d3e469d300e6e48a048a guid: 1f39500a9deabad43b87bc76122646fc
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@ -0,0 +1,15 @@
using Newtonsoft.Json.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace NEG.Utils.Achievments.AchievmentTypes
{
public abstract class AchievmentDefinition : ScriptableObject
{
[field: SerializeField]
public string Id { get; private set; }
public abstract AchievmentData Construct();
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: f9bd42dd58472044b8ecc0d69caa7da8 guid: 4aef60a6b4e41e243845a476862049e1
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@ -0,0 +1,35 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace NEG.Utils.Achievments.AchievmentTypes
{
public class FloatAchievmentData : AchievmentData
{
public override bool IsCompleted => CurrentProgress >= Def.ProgressRequired;
public float CurrentProgress
{
get => currentProgress;
internal set
{
if (Def.Clamped)
{
value = Mathf.Max(value, Def.LowerBound);
}
currentProgress = Mathf.Min(value, Def.ProgressRequired);
}
}
public float ProgressLeft => Def.ProgressRequired - CurrentProgress;
private FloatAchievmentDefinition Def => (FloatAchievmentDefinition)Achivment;
private float currentProgress;
public FloatAchievmentData(FloatAchievmentDefinition def) : base(def)
{
currentProgress = def.InitialProgress;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 32572809c0644434d8e64878a3c22f0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,29 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace NEG.Utils.Achievments.AchievmentTypes
{
[CreateAssetMenu(menuName = "Achievments/Float Achievment")]
public class FloatAchievmentDefinition : AchievmentDefinition
{
[field: Tooltip("Amount of progress required for completion, required to be at leas 1, otherwise would be considered completed from the beginning")]
[field: Min(0)]
[field: SerializeField]
public float ProgressRequired { get; private set; } = 1;
[field: SerializeField]
public float InitialProgress { get; private set; } = 0;
[field: SerializeField]
public bool Clamped { get; private set; } = false;
public float LowerBound { get; private set; } = 0;
public override AchievmentData Construct()
{
return new FloatAchievmentData(this);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2d7270d5452c9b04ca07ef43a491a18d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using UnityEngine;
namespace NEG.Utils.Achievments.AchievmentTypes
{
public class IntAchievmentData : AchievmentData
{
public override bool IsCompleted => CurrentProgress >= Def.ProgressRequired;
public int CurrentProgress
{
get => currentProgress;
internal set
{
if (Def.Clamped)
{
value = Mathf.Max(value, Def.LowerBound);
}
currentProgress = Mathf.Min(value, Def.ProgressRequired);
}
}
public int ProgressLeft => Def.ProgressRequired - CurrentProgress;
private IntAchievmentDefinition Def => (IntAchievmentDefinition)Achivment;
private int currentProgress;
public IntAchievmentData(IntAchievmentDefinition def) : base(def)
{
currentProgress = def.InitialProgress;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0a121b6e6fa8ecc45ab4506c15e5f46e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,27 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace NEG.Utils.Achievments.AchievmentTypes
{
[CreateAssetMenu(menuName = "Achievments/Int Achievment")]
public class IntAchievmentDefinition : AchievmentDefinition
{
[field: Tooltip("Amount of progress required for completion, required to be at leas 1, otherwise would be considered completed from the beginning")]
[field: SerializeField]
public int ProgressRequired { get; private set; } = 1;
[field: SerializeField]
public int InitialProgress { get; private set; } = 0;
[field: SerializeField]
public bool Clamped { get; private set; } = false;
public int LowerBound { get; private set; } = 0;
public override AchievmentData Construct()
{
return new IntAchievmentData(this);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5318fea685aa56646a3310c38a9a9bac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace NEG.Utils.Achievments.AchievmentTypes
{
public class ToggleAchievmentData : AchievmentData
{
public override bool IsCompleted => CompletionState;
public bool CompletionState { get; internal set; } = false;
public ToggleAchievmentData(ToggleAchievmentDefinition def) : base(def)
{
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e0806da00902994f9aeb26d295956f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,15 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace NEG.Utils.Achievments.AchievmentTypes
{
[CreateAssetMenu(menuName = "Achievments/Toggle Achievment")]
public class ToggleAchievmentDefinition : AchievmentDefinition
{
public override AchievmentData Construct()
{
return new ToggleAchievmentData(this);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 608c7e921b8b16b42919fc6f55b67fcb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -2,12 +2,14 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace NEG.Utils.Achivments namespace NEG.Utils.Achievments
{ {
using AchievmentTypes;
[CreateAssetMenu(menuName = "Achivments/Collection")] [CreateAssetMenu(menuName = "Achivments/Collection")]
public class AchivmentDefinitionCollection : ScriptableObject public class AchivmentDefinitionCollection : ScriptableObject
{ {
[field: SerializeField] [field: SerializeField]
public List<AchivmentDefinition> Achivments { get; private set; } = new List<AchivmentDefinition>(); public List<AchievmentDefinition> Achivments { get; private set; } = new List<AchievmentDefinition>();
} }
} }

View File

@ -2,7 +2,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace NEG.Utils.Achivments namespace NEG.Utils.Achievments
{ {
[CreateAssetMenu(menuName = "Achivments/BaseConfig")] [CreateAssetMenu(menuName = "Achivments/BaseConfig")]
public class AchivmentManagerConfig : ScriptableObject, IAchivmentManagerConfig public class AchivmentManagerConfig : ScriptableObject, IAchivmentManagerConfig
@ -12,12 +12,12 @@ namespace NEG.Utils.Achivments
[field: SerializeField] [field: SerializeField]
public string AchivmentCollectionAssetLabel { get; private set; } = DefaultAchivmentsCollectionLabel; public string AchivmentCollectionAssetLabel { get; private set; } = DefaultAchivmentsCollectionLabel;
public void Apply(AchivmentManager.Builder builder) public void Apply(AchievmentManager.Builder builder)
{ {
builder.WithLabeledDefinitions(AchivmentCollectionAssetLabel); builder.WithLabeledDefinitions(AchivmentCollectionAssetLabel);
} }
public void ApplyLast(AchivmentManager.Builder builder) public void ApplyLast(AchievmentManager.Builder builder)
{ {
} }

View File

@ -0,0 +1,23 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace NEG.Utils.Achievments
{
using AchievmentTypes;
public interface IAchievmentCallbackReciever
{
/// <summary>
/// Called when an achivment is completed
/// </summary>
/// <param name="achivment"></param>
void AchievmentCompleted(AchievmentData achivment);
/// <summary>
/// Called when achivment progress changes
/// </summary>
/// <param name="achivment"></param>
void AchievmentStateChanged(AchievmentData achivment);
}
}

View File

@ -2,7 +2,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace NEG.Utils.Achivments namespace NEG.Utils.Achievments
{ {
public interface IAchivmentManagerConfig public interface IAchivmentManagerConfig
{ {
@ -10,12 +10,12 @@ namespace NEG.Utils.Achivments
/// Used to Apply config data /// Used to Apply config data
/// </summary> /// </summary>
/// <param name="builder"></param> /// <param name="builder"></param>
void Apply(AchivmentManager.Builder builder); void Apply(AchievmentManager.Builder builder);
/// <summary> /// <summary>
/// Called after <see cref="Apply(AchivmentManager.Builder)"/> was called on every other config /// Called after <see cref="Apply(AchievmentManager.Builder)"/> was called on every other config
/// </summary> /// </summary>
/// <param name="builder"></param> /// <param name="builder"></param>
void ApplyLast(AchivmentManager.Builder builder); void ApplyLast(AchievmentManager.Builder builder);
} }
} }

View File

@ -2,7 +2,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace NEG.Utils.Achivments namespace NEG.Utils.Achievments
{ {
public interface IAchivmentStorage public interface IAchivmentStorage
{ {

View File

@ -1,22 +0,0 @@
using Newtonsoft.Json.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace NEG.Utils.Achivments
{
public class AchivmentData
{
public AchivmentDefinition Achivment { get; private set; }
public int ProgressLeft { get; internal set; }
public bool IsCompleted => ProgressLeft <= 0;
public AchivmentData(AchivmentDefinition achivment, int progressLeft)
{
Achivment = achivment;
ProgressLeft = progressLeft;
}
}
}

View File

@ -1,34 +0,0 @@
using Newtonsoft.Json.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace NEG.Utils.Achivments
{
[CreateAssetMenu(menuName = "Achivments/AchivmentDefinition")]
public class AchivmentDefinition : ScriptableObject
{
public JToken AdditionalData
{
get
{
additionalData ??= JObject.Parse(additionalDataInitializer);
return additionalData;
}
}
[field: SerializeField]
public string Id { get; private set; }
[field: Tooltip("Amount of progress required for completion, required to be at leas 1, otherwise would be considered completed from the beginning")]
[field: Min(1)]
[field: SerializeField]
public int ProgressRequired { get; private set; } = 1;
[SerializeField]
[Tooltip("Temporary until json editor is a thing")]
private string additionalDataInitializer;
private JToken additionalData;
}
}

View File

@ -1,193 +0,0 @@
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.Achivments
{
public class AchivmentManager
{
public class Builder
{
public const string DefaultAchivmentsConfigLabel = "Achivments";
private AchivmentManager manager = new AchivmentManager();
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(IAchivmentCallbackReciever callbackReciever)
{
manager.AddCallbackReciever(callbackReciever);
return this;
}
public AchivmentManager Build()
{
if (storage != null)
{
manager.LoadFrom(storage);
}
return manager;
}
}
public delegate void AchivmentSetCallback(AchivmentData achivment);
public delegate void AchivmentProgressSetCallback(AchivmentData achivment, int progressLeft);
public event AchivmentSetCallback AchivmentSet;
public event AchivmentProgressSetCallback AchivmentProgressSet;
private Dictionary<string, AchivmentDefinition> definitionCache;
private Dictionary<AchivmentDefinition, AchivmentData> dataCache;
private AchivmentManager()
{
definitionCache = new Dictionary<string, AchivmentDefinition>();
dataCache = new Dictionary<AchivmentDefinition, AchivmentData>();
}
private void RegisterAchivment(AchivmentDefinition definition)
{
if (!definitionCache.ContainsKey(definition.Id))
{
definitionCache.Add(definition.Id, definition);
dataCache.Add(definition, new AchivmentData(definition, definition.ProgressRequired));
}
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<IAchivmentCallbackReciever> initialCallbacks)
{
foreach (var callback in initialCallbacks)
{
AddCallbackReciever(callback);
}
}
public void AddCallbackReciever(IAchivmentCallbackReciever callback)
{
AchivmentSet += callback.SetAchivment;
AchivmentProgressSet += callback.SetAchivmentProgress;
}
public void RemoveCallbackReciever(IAchivmentCallbackReciever callback)
{
AchivmentSet -= callback.SetAchivment;
AchivmentProgressSet -= callback.SetAchivmentProgress;
}
public void SetAchivment(string id)
{
var data = GetAchivmentForId(id);
data.ProgressLeft = 0;
AchivmentSet?.Invoke(data);
}
public void AddProgress(string id, int amount)
{
var data = GetAchivmentForId(id);
if (data.IsCompleted)
{
return;
}
data.ProgressLeft = Mathf.Max(data.ProgressLeft - amount, 0);
AchivmentProgressSet?.Invoke(data, data.Achivment.ProgressRequired - data.ProgressLeft);
if (data.IsCompleted)
{
AchivmentSet?.Invoke(data);
}
}
private AchivmentData GetAchivmentForId(string id)
{
var def = definitionCache.GetValueOrDefault(id);
if (def != null)
{
return dataCache[def];
}
else
{
throw new ApplicationException($"Invalid achivment id {id}");
}
}
}
}

View File

@ -1,23 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace NEG.Utils.Achivments
{
public interface IAchivmentCallbackReciever
{
/// <summary>
/// Called when an achivment is completed
/// </summary>
/// <param name="achivment"></param>
/// <param name="getState"></param>
void SetAchivment(AchivmentData achivment);
/// <summary>
/// Called when achivment progress changes
/// </summary>
/// <param name="achivment"></param>
/// <param name="progress">Current achivment progress, equal to <paramref name="achivment"/> <see cref="AchivmentDefinition.ProgressRequired"/> - <see cref="AchivmentData.ProgressLeft"/></param>
void SetAchivmentProgress(AchivmentData achivment, int progress);
}
}