Added Feel plugin

This commit is contained in:
journaliciouz
2025-12-11 14:49:16 +01:00
parent 97dce4aaf6
commit 1942a531d4
2820 changed files with 257786 additions and 9 deletions

View File

@@ -0,0 +1,117 @@
using UnityEngine;
using System.Collections;
using System;
namespace MoreMountains.Tools
{
/// <summary>
/// This achievement system supports 2 types of achievements : simple (do something > get achievement), and progress based (jump X times, kill X enemies, etc).
/// </summary>
public enum AchievementTypes { Simple, Progress }
[Serializable]
public class MMAchievement
{
[Header("Identification")]
/// the unique identifier for this achievement
public string AchievementID;
/// is this achievement progress based or
public AchievementTypes AchievementType;
/// if this is true, the achievement won't be displayed in a list
public bool HiddenAchievement;
/// if this is true, the achievement has been unlocked. Otherwise, it's still up for grabs
public bool UnlockedStatus;
[Header("Description")]
/// the achievement's name/title
public string Title;
/// the achievement's description
public string Description;
/// the amount of points unlocking this achievement gets you
public int Points;
[Header("Image and Sounds")]
/// the image to display while this achievement is locked
public Sprite LockedImage;
/// the image to display when the achievement is unlocked
public Sprite UnlockedImage;
/// a sound to play when the achievement is unlocked
public AudioClip UnlockedSound;
[Header("Progress")]
/// the amount of progress needed to unlock this achievement.
public int ProgressTarget;
/// the current amount of progress made on this achievement
public int ProgressCurrent;
/// <summary>
/// Unlocks the achievement, asks for a save of the current achievements, and triggers an MMAchievementUnlockedEvent for this achievement.
/// This will usually then be caught by the MMAchievementDisplay class.
/// </summary>
public virtual void UnlockAchievement()
{
// if the achievement has already been unlocked, we do nothing and exit
if (UnlockedStatus)
{
return;
}
UnlockedStatus = true;
MMGameEvent.Trigger("Save");
MMAchievementUnlockedEvent.Trigger(this);
}
/// <summary>
/// Locks the achievement.
/// </summary>
public virtual void LockAchievement()
{
UnlockedStatus = false;
}
/// <summary>
/// Adds the specified value to the current progress.
/// </summary>
/// <param name="newProgress">New progress.</param>
public virtual void AddProgress(int newProgress)
{
ProgressCurrent += newProgress;
EvaluateProgress();
}
/// <summary>
/// Sets the progress to the value passed in parameter.
/// </summary>
/// <param name="newProgress">New progress.</param>
public virtual void SetProgress(int newProgress)
{
ProgressCurrent = newProgress;
EvaluateProgress();
}
/// <summary>
/// Evaluates the current progress of the achievement, and unlocks it if needed.
/// </summary>
protected virtual void EvaluateProgress()
{
MMAchievementChangedEvent.Trigger(this);
if (ProgressCurrent >= ProgressTarget)
{
ProgressCurrent = ProgressTarget;
UnlockAchievement();
}
}
/// <summary>
/// Copies this achievement (useful when loading from a scriptable object list)
/// </summary>
public virtual MMAchievement Copy()
{
MMAchievement clone = new MMAchievement ();
// we use Json utility to store a copy of our achievement, not a reference
clone = JsonUtility.FromJson<MMAchievement>(JsonUtility.ToJson(this));
return clone;
}
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 12a7d0ec5f1424c129527c01a444e964
timeCreated: 1480085861
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Foundation/MMAchievements/Scripts/MMAchievement.cs
uploadId: 830868

View File

@@ -0,0 +1,23 @@
#if MM_UI
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using MoreMountains.Tools;
namespace MoreMountains.Tools
{
/// <summary>
/// This class is used to display an achievement. Add it to a prefab containing all the required elements listed below.
/// </summary>
[AddComponentMenu("More Mountains/Tools/Achievements/MM Achievement Display Item")]
public class MMAchievementDisplayItem : MonoBehaviour
{
public Image BackgroundLocked;
public Image BackgroundUnlocked;
public Image Icon;
public Text Title;
public Text Description;
public MMProgressBar ProgressBarDisplay;
}
}
#endif

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 4e775494159ed5e4c997035d2df49776
timeCreated: 1480176460
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Foundation/MMAchievements/Scripts/MMAchievementDisplayItem.cs
uploadId: 830868

View File

@@ -0,0 +1,105 @@
using UnityEngine;
using System.Collections;
#if MM_UI
namespace MoreMountains.Tools
{
/// <summary>
/// A class used to display the achievements on screen.
/// The AchievementDisplayItems will be parented to it, so it's better if it has a LayoutGroup (Vertical or Horizontal) too.
/// </summary>
[AddComponentMenu("More Mountains/Tools/Achievements/MM Achievement Displayer")]
public class MMAchievementDisplayer : MonoBehaviour, MMEventListener<MMAchievementUnlockedEvent>
{
[Header("Achievements")]
/// the prefab to use to display achievements
public MMAchievementDisplayItem AchievementDisplayPrefab;
/// the duration the achievement will remain on screen for when unlocked
public float AchievementDisplayDuration = 5f;
/// the fade in/out speed
public float AchievementFadeDuration = 0.2f;
protected WaitForSeconds _achievementFadeOutWFS;
/// <summary>
/// Instantiates an achievement display prefab and shows it for the specified duration
/// </summary>
/// <returns>The achievement.</returns>
/// <param name="achievement">Achievement.</param>
public virtual IEnumerator DisplayAchievement(MMAchievement achievement)
{
if ((this.transform == null) || (AchievementDisplayPrefab == null))
{
yield break;
}
// we instantiate our achievement display prefab, and add it to the group that will automatically handle its position
GameObject instance = (GameObject)Instantiate(AchievementDisplayPrefab.gameObject);
instance.transform.SetParent(this.transform,false);
// we get the achievement displayer
MMAchievementDisplayItem achievementDisplay = instance.GetComponent<MMAchievementDisplayItem> ();
if (achievementDisplay == null)
{
yield break;
}
// we fill our achievement
achievementDisplay.Title.text = achievement.Title;
achievementDisplay.Description.text = achievement.Description;
achievementDisplay.Icon.sprite = achievement.UnlockedImage;
if (achievement.AchievementType == AchievementTypes.Progress)
{
achievementDisplay.ProgressBarDisplay.gameObject.SetActive(true);
}
else
{
achievementDisplay.ProgressBarDisplay.gameObject.SetActive(false);
}
// we play a sound if set
if (achievement.UnlockedSound != null)
{
MMSfxEvent.Trigger (achievement.UnlockedSound);
}
// we fade it in and out
CanvasGroup achievementCanvasGroup = instance.GetComponent<CanvasGroup> ();
if (achievementCanvasGroup != null)
{
achievementCanvasGroup.alpha = 0;
StartCoroutine(MMFade.FadeCanvasGroup(achievementCanvasGroup, AchievementFadeDuration, 1));
yield return _achievementFadeOutWFS;
StartCoroutine(MMFade.FadeCanvasGroup(achievementCanvasGroup, AchievementFadeDuration, 0));
}
}
/// <summary>
/// When an achievement is unlocked, we display it
/// </summary>
/// <param name="achievementUnlockedEvent">Achievement unlocked event.</param>
public virtual void OnMMEvent(MMAchievementUnlockedEvent achievementUnlockedEvent)
{
StartCoroutine(DisplayAchievement (achievementUnlockedEvent.Achievement));
}
/// <summary>
/// On enable, we start listening for unlocked achievements
/// </summary>
protected virtual void OnEnable()
{
this.MMEventStartListening<MMAchievementUnlockedEvent>();
_achievementFadeOutWFS = new WaitForSeconds (AchievementFadeDuration + AchievementDisplayDuration);
}
/// <summary>
/// On disable, we stop listening for unlocked achievements
/// </summary>
protected virtual void OnDisable()
{
this.MMEventStopListening<MMAchievementUnlockedEvent>();
}
}
}
#endif

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: f759f536b039f2b46881cfc8ea77c496
timeCreated: 1480176245
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Foundation/MMAchievements/Scripts/MMAchievementDisplayer.cs
uploadId: 830868

View File

@@ -0,0 +1,53 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace MoreMountains.Tools
{
/// <summary>
/// An event type used to broadcast the fact that an achievement has been unlocked
/// </summary>
public struct MMAchievementUnlockedEvent
{
/// the achievement that has been unlocked
public MMAchievement Achievement;
/// <summary>
/// Constructor
/// </summary>
/// <param name="newAchievement">New achievement.</param>
public MMAchievementUnlockedEvent(MMAchievement newAchievement)
{
Achievement = newAchievement;
}
static MMAchievementUnlockedEvent e;
public static void Trigger(MMAchievement newAchievement)
{
e.Achievement = newAchievement;
MMEventManager.TriggerEvent(e);
}
}
public struct MMAchievementChangedEvent
{
/// the achievement that has been unlocked
public MMAchievement Achievement;
/// <summary>
/// Constructor
/// </summary>
/// <param name="newAchievement">New achievement.</param>
public MMAchievementChangedEvent(MMAchievement newAchievement)
{
Achievement = newAchievement;
}
static MMAchievementChangedEvent e;
public static void Trigger(MMAchievement newAchievement)
{
e.Achievement = newAchievement;
MMEventManager.TriggerEvent(e);
}
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: d8ab561ba77c94d3fafd05ffde7d9f7c
timeCreated: 1480085871
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Foundation/MMAchievements/Scripts/MMAchievementEvent.cs
uploadId: 830868

View File

@@ -0,0 +1,34 @@
using UnityEngine;
using System.Collections;
using MoreMountains.Tools;
using System.Collections.Generic;
namespace MoreMountains.Tools
{
[CreateAssetMenu(fileName="AchievementList",menuName="MoreMountains/Achievement List")]
/// <summary>
/// A scriptable object containing a list of achievements. You need to create one and store it in a Resources folder for this to work.
/// </summary>
public class MMAchievementList : ScriptableObject
{
/// the unique ID of this achievement list. This is used to save/load data.
public string AchievementsListID = "AchievementsList";
/// the list of achievements
public List<MMAchievement> Achievements;
/// <summary>
/// Asks for a reset of all the achievements in this list (they'll all be locked again, their progress lost).
/// </summary>
public virtual void ResetAchievements()
{
Debug.LogFormat ("Reset Achievements");
MMAchievementManager.ResetAchievements (AchievementsListID);
}
private MMReferenceHolder<MMAchievementList> _instances;
protected virtual void OnEnable() { _instances.Reference(this); }
protected virtual void OnDisable() { _instances.Dispose(); }
public static MMAchievementList Any => MMReferenceHolder<MMAchievementList>.Any;
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 6f8f916bd4c5a444fa42e298d90f488d
timeCreated: 1480177569
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Foundation/MMAchievements/Scripts/MMAchievementList.cs
uploadId: 830868

View File

@@ -0,0 +1,225 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace MoreMountains.Tools
{
[ExecuteAlways]
/// <summary>
/// This static class is in charge of storing the current state of the achievements, unlocking/locking them, and saving them to data files
/// </summary>
public static class MMAchievementManager
{
public static List<MMAchievement> AchievementsList { get { return _achievements; }}
private static List<MMAchievement> _achievements;
private static MMAchievement _achievement = null;
public static string _defaultFileName = "Achievements";
public static string _saveFolderName = "MMAchievements/";
public static string _saveFileExtension = ".achievements";
public static string SaveFileName;
public static string ListID;
/// <summary>
/// You'll need to call this method to initialize the manager
/// </summary>
public static void LoadAchievementList(MMAchievementList achievementList)
{
_achievements = new List<MMAchievement> ();
if (achievementList == null)
{
return;
}
// we store the ID for save purposes
ListID = achievementList.AchievementsListID;
foreach (MMAchievement achievement in achievementList.Achievements)
{
_achievements.Add (achievement.Copy());
}
}
/// <summary>
/// Unlocks the specified achievement (if found).
/// </summary>
/// <param name="achievementID">Achievement I.</param>
public static void UnlockAchievement(string achievementID)
{
_achievement = AchievementManagerContains(achievementID);
if (_achievement != null)
{
_achievement.UnlockAchievement();
}
}
/// <summary>
/// Locks the specified achievement (if found).
/// </summary>
/// <param name="achievementID">Achievement ID.</param>
public static void LockAchievement(string achievementID)
{
_achievement = AchievementManagerContains(achievementID);
if (_achievement != null)
{
_achievement.LockAchievement();
}
}
/// <summary>
/// Adds progress to the specified achievement (if found).
/// </summary>
/// <param name="achievementID">Achievement ID.</param>
/// <param name="newProgress">New progress.</param>
public static void AddProgress(string achievementID, int newProgress)
{
_achievement = AchievementManagerContains(achievementID);
if (_achievement != null)
{
_achievement.AddProgress(newProgress);
}
}
/// <summary>
/// Sets the progress of the specified achievement (if found) to the specified progress.
/// </summary>
/// <param name="achievementID">Achievement ID.</param>
/// <param name="newProgress">New progress.</param>
public static void SetProgress(string achievementID, int newProgress)
{
_achievement = AchievementManagerContains(achievementID);
if (_achievement != null)
{
_achievement.SetProgress(newProgress);
}
}
/// <summary>
/// Determines if the achievement manager contains an achievement of the specified ID. Returns it if found, otherwise returns null
/// </summary>
/// <returns>The achievement corresponding to the searched ID if found, otherwise null.</returns>
/// <param name="searchedID">Searched I.</param>
private static MMAchievement AchievementManagerContains(string searchedID)
{
if (_achievements.Count == 0)
{
return null;
}
foreach(MMAchievement achievement in _achievements)
{
if (achievement.AchievementID == searchedID)
{
return achievement;
}
}
return null;
}
// SAVE ------------------------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Removes saved data and resets all achievements from a list
/// </summary>
/// <param name="listID">The ID of the achievement list to reset.</param>
public static void ResetAchievements(string listID)
{
if (_achievements != null)
{
foreach(MMAchievement achievement in _achievements)
{
achievement.ProgressCurrent = 0;
achievement.UnlockedStatus = false;
}
}
DeterminePath (listID);
MMSaveLoadManager.DeleteSave(SaveFileName + _saveFileExtension, _saveFolderName);
Debug.LogFormat ("Achievements Reset");
}
public static void ResetAllAchievements()
{
ResetAchievements (ListID);
}
/// <summary>
/// Loads the saved achievements file and updates the array with its content.
/// </summary>
public static void LoadSavedAchievements()
{
DeterminePath ();
SerializedMMAchievementManager serializedMMAchievementManager = (SerializedMMAchievementManager)MMSaveLoadManager.Load(typeof(SerializedMMAchievementManager), SaveFileName+ _saveFileExtension, _saveFolderName);
ExtractSerializedMMAchievementManager(serializedMMAchievementManager);
}
/// <summary>
/// Saves the achievements current status to a file on disk
/// </summary>
public static void SaveAchievements()
{
DeterminePath ();
SerializedMMAchievementManager serializedMMAchievementManager = new SerializedMMAchievementManager();
FillSerializedMMAchievementManager(serializedMMAchievementManager);
MMSaveLoadManager.Save(serializedMMAchievementManager, SaveFileName+_saveFileExtension, _saveFolderName);
}
/// <summary>
/// Determines the path the achievements save file should be saved to.
/// </summary>
private static void DeterminePath(string specifiedFileName = "")
{
string tempFileName = (!string.IsNullOrEmpty(ListID)) ? ListID : _defaultFileName;
if (!string.IsNullOrEmpty(specifiedFileName))
{
tempFileName = specifiedFileName;
}
SaveFileName = tempFileName;
}
/// <summary>
/// Serializes the contents of the achievements array to a serialized, ready to save object
/// </summary>
/// <param name="serializedInventory">Serialized inventory.</param>
public static void FillSerializedMMAchievementManager(SerializedMMAchievementManager serializedAchievements)
{
serializedAchievements.Achievements = new SerializedMMAchievement[_achievements.Count];
for (int i = 0; i < _achievements.Count(); i++)
{
SerializedMMAchievement newAchievement = new SerializedMMAchievement (_achievements[i].AchievementID, _achievements[i].UnlockedStatus, _achievements[i].ProgressCurrent);
serializedAchievements.Achievements [i] = newAchievement;
}
}
/// <summary>
/// Extracts the serialized achievements into our achievements array if the achievements ID match.
/// </summary>
/// <param name="serializedAchievements">Serialized achievements.</param>
public static void ExtractSerializedMMAchievementManager(SerializedMMAchievementManager serializedAchievements)
{
if (serializedAchievements == null)
{
return;
}
for (int i = 0; i < _achievements.Count(); i++)
{
for (int j=0; j<serializedAchievements.Achievements.Length; j++)
{
if (_achievements[i].AchievementID == serializedAchievements.Achievements[j].AchievementID)
{
_achievements [i].UnlockedStatus = serializedAchievements.Achievements [j].UnlockedStatus;
_achievements [i].ProgressCurrent = serializedAchievements.Achievements [j].ProgressCurrent;
}
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 7190b0080c757413082e75f41313dee8
timeCreated: 1480085861
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Foundation/MMAchievements/Scripts/MMAchievementManager.cs
uploadId: 830868

View File

@@ -0,0 +1,81 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace MoreMountains.Tools
{
/// <summary>
/// That class is meant to be extended to implement the achievement rules specific to your game.
/// </summary>
public abstract class MMAchievementRules : MonoBehaviour, MMEventListener<MMGameEvent>
{
public MMAchievementList AchievementList;
[MMInspectorButton("PrintCurrentStatus")]
public bool PrintCurrentStatusBtn;
public virtual void PrintCurrentStatus()
{
foreach (MMAchievement achievement in MMAchievementManager.AchievementsList)
{
string status = achievement.UnlockedStatus ? "unlocked" : "locked";
MMDebug.DebugLogInfo("["+achievement.AchievementID + "] "+achievement.Title+", status : "+status+", progress : "+achievement.ProgressCurrent+"/"+achievement.ProgressTarget);
}
}
/// <summary>
/// On Awake, loads the achievement list and the saved file
/// </summary>
protected virtual void Awake()
{
// we load the list of achievements, stored in a ScriptableObject in our Resources folder.
MMAchievementManager.LoadAchievementList (AchievementList);
// we load our saved file, to update that list with the saved values.
MMAchievementManager.LoadSavedAchievements ();
}
/// <summary>
/// On enable, we start listening for MMGameEvents. You may want to extend that to listen to other types of events.
/// </summary>
protected virtual void OnEnable()
{
this.MMEventStartListening<MMGameEvent>();
}
/// <summary>
/// On disable, we stop listening for MMGameEvents. You may want to extend that to stop listening to other types of events.
/// </summary>
protected virtual void OnDisable()
{
this.MMEventStopListening<MMGameEvent>();
}
/// <summary>
/// When we catch an MMGameEvent, we do stuff based on its name
/// </summary>
/// <param name="gameEvent">Game event.</param>
public virtual void OnMMEvent(MMGameEvent gameEvent)
{
switch (gameEvent.EventName)
{
case "Save":
MMAchievementManager.SaveAchievements ();
break;
/*
// These are just examples of how you could catch a GameStart MMGameEvent and trigger the potential unlock of a corresponding achievement
case "GameStart":
MMAchievementManager.UnlockAchievement("theFirestarter");
break;
case "LifeLost":
MMAchievementManager.UnlockAchievement("theEndOfEverything");
break;
case "Pause":
MMAchievementManager.UnlockAchievement("timeStop");
break;
case "Jump":
MMAchievementManager.UnlockAchievement ("aSmallStepForMan");
MMAchievementManager.AddProgress ("toInfinityAndBeyond", 1);
break;*/
}
}
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 2c7c529377dc843a9a2316262550c4d6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- AchievementList: {fileID: 11400000, guid: 18225175314a9854581d8d6262302958, type: 2}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Foundation/MMAchievements/Scripts/MMAchievementRules.cs
uploadId: 830868

View File

@@ -0,0 +1,39 @@
using UnityEngine;
using System.Collections;
using System;
namespace MoreMountains.Tools
{
[Serializable]
/// <summary>
/// A serializable class used to store an achievement into a save file
/// </summary>
public class SerializedMMAchievement
{
public string AchievementID;
public bool UnlockedStatus;
public int ProgressCurrent;
/// <summary>
/// Initializes a new instance of the <see cref="MoreMountains.Tools.SerializedMMAchievement"/> class.
/// </summary>
/// <param name="achievementID">Achievement I.</param>
/// <param name="unlockedStatus">If set to <c>true</c> unlocked status.</param>
/// <param name="progressCurrent">Progress current.</param>
public SerializedMMAchievement(string achievementID, bool unlockedStatus, int progressCurrent)
{
AchievementID = achievementID;
UnlockedStatus = unlockedStatus;
ProgressCurrent = progressCurrent;
}
}
[Serializable]
/// <summary>
/// Serializable MM achievement manager.
/// </summary>
public class SerializedMMAchievementManager
{
public SerializedMMAchievement[] Achievements;
}
}

View File

@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 24f964aa3d55241b591bc1da7903819e
timeCreated: 1480085861
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 183370
packageName: Feel
packageVersion: 5.9.1
assetPath: Assets/Feel/MMTools/Foundation/MMAchievements/Scripts/SerializedMMAchievementManager.cs
uploadId: 830868