加入用户最近100场战绩记录并实现展示
This commit is contained in:
@@ -0,0 +1,656 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
public sealed class StoreOwnershipLedger : MonoBehaviour
|
||||
{
|
||||
private const string RuntimeStoreItemResourcesPath = "so/storeSO";
|
||||
|
||||
public static StoreOwnershipLedger Instance { get; private set; }
|
||||
|
||||
private readonly Dictionary<int, StoreOwnershipEntry> entriesByItemId = new Dictionary<int, StoreOwnershipEntry>();
|
||||
private readonly List<storeItemSO> cachedStoreItems = new List<storeItemSO>();
|
||||
private bool initialized;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
private static void Bootstrap()
|
||||
{
|
||||
EnsureInstance();
|
||||
}
|
||||
|
||||
public static StoreOwnershipLedger EnsureInstance()
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
return Instance;
|
||||
}
|
||||
|
||||
var host = new GameObject("__runtime_ownership_bridge");
|
||||
host.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector;
|
||||
DontDestroyOnLoad(host);
|
||||
Instance = host.AddComponent<StoreOwnershipLedger>();
|
||||
return Instance;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
InitializeIfNeeded();
|
||||
}
|
||||
|
||||
private void OnApplicationPause(bool pauseStatus)
|
||||
{
|
||||
if (pauseStatus)
|
||||
{
|
||||
SaveNow();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
SaveNow();
|
||||
}
|
||||
|
||||
public void InitializeIfNeeded()
|
||||
{
|
||||
if (initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StoreOwnershipPayload payload;
|
||||
StoreOwnershipStorage.TryLoad(out payload);
|
||||
RebuildFromPayload(payload);
|
||||
LoadStoreItems();
|
||||
SeedFromCurrentMirrorFlags();
|
||||
SyncAllMirrorFlags();
|
||||
initialized = true;
|
||||
SaveNow();
|
||||
}
|
||||
|
||||
public void ForceSyncMirrorFlags()
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
LoadStoreItems();
|
||||
SyncAllMirrorFlags();
|
||||
}
|
||||
|
||||
public bool IsOwned(storeItemSO itemSO)
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
if (itemSO == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StoreOwnershipEntry entry;
|
||||
entriesByItemId.TryGetValue(itemSO.itemID, out entry);
|
||||
|
||||
switch (itemSO.itemType)
|
||||
{
|
||||
case storeItemSO.ItemType.character:
|
||||
return (entry != null && entry.owned) || (itemSO.associatedAllyHero != null && itemSO.associatedAllyHero.isUnlocked);
|
||||
case storeItemSO.ItemType.song:
|
||||
return (entry != null && entry.owned) || (itemSO.associatedSong != null && itemSO.associatedSong.isUnlocked);
|
||||
case storeItemSO.ItemType.storyPassage:
|
||||
return IsStoryGrantOwned(itemSO, entry);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGrantOwnership(storeItemSO itemSO, out string failureMessage)
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
failureMessage = string.Empty;
|
||||
|
||||
if (!ValidateGrantTarget(itemSO, out failureMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var entry = GetOrCreateEntry(itemSO.itemID);
|
||||
switch (itemSO.itemType)
|
||||
{
|
||||
case storeItemSO.ItemType.character:
|
||||
case storeItemSO.ItemType.song:
|
||||
entry.owned = true;
|
||||
break;
|
||||
|
||||
case storeItemSO.ItemType.storyPassage:
|
||||
GrantStoryOwnership(itemSO, entry);
|
||||
break;
|
||||
|
||||
default:
|
||||
failureMessage = "当前未配置发放逻辑";
|
||||
return false;
|
||||
}
|
||||
|
||||
ApplyEntryToItem(itemSO, entry);
|
||||
SaveNow();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGrantOwnershipPreview(storeItemSO itemSO, out string failureMessage)
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
return ValidateGrantTarget(itemSO, out failureMessage);
|
||||
}
|
||||
|
||||
public void SaveNow()
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StoreOwnershipStorage.TrySave(CreatePayload());
|
||||
}
|
||||
|
||||
private void RebuildFromPayload(StoreOwnershipPayload payload)
|
||||
{
|
||||
entriesByItemId.Clear();
|
||||
if (payload == null || payload.entries == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < payload.entries.Count; i++)
|
||||
{
|
||||
var entry = payload.entries[i];
|
||||
if (entry == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.unlockedStorySonIds == null)
|
||||
{
|
||||
entry.unlockedStorySonIds = new List<int>();
|
||||
}
|
||||
|
||||
entriesByItemId[entry.storeItemId] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
private StoreOwnershipPayload CreatePayload()
|
||||
{
|
||||
var payload = StoreOwnershipStorage.CreateDefaultPayload();
|
||||
foreach (var pair in entriesByItemId)
|
||||
{
|
||||
payload.entries.Add(CloneEntry(pair.Value));
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
private void LoadStoreItems()
|
||||
{
|
||||
cachedStoreItems.Clear();
|
||||
var loadedItems = Resources.LoadAll<storeItemSO>(RuntimeStoreItemResourcesPath);
|
||||
var seen = new HashSet<int>();
|
||||
for (int i = 0; i < loadedItems.Length; i++)
|
||||
{
|
||||
var item = loadedItems[i];
|
||||
if (item == null || !seen.Add(item.itemID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cachedStoreItems.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void SeedFromCurrentMirrorFlags()
|
||||
{
|
||||
bool changed = false;
|
||||
for (int i = 0; i < cachedStoreItems.Count; i++)
|
||||
{
|
||||
var item = cachedStoreItems[i];
|
||||
if (item == null || entriesByItemId.ContainsKey(item.itemID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
StoreOwnershipEntry seededEntry = TrySeedEntry(item);
|
||||
if (seededEntry == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
entriesByItemId[item.itemID] = seededEntry;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
SaveNow();
|
||||
}
|
||||
}
|
||||
|
||||
private StoreOwnershipEntry TrySeedEntry(storeItemSO itemSO)
|
||||
{
|
||||
if (itemSO == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (itemSO.itemType)
|
||||
{
|
||||
case storeItemSO.ItemType.character:
|
||||
if (itemSO.associatedAllyHero != null && itemSO.associatedAllyHero.isUnlocked)
|
||||
{
|
||||
return new StoreOwnershipEntry { storeItemId = itemSO.itemID, owned = true };
|
||||
}
|
||||
break;
|
||||
|
||||
case storeItemSO.ItemType.song:
|
||||
if (itemSO.associatedSong != null && itemSO.associatedSong.isUnlocked)
|
||||
{
|
||||
return new StoreOwnershipEntry { storeItemId = itemSO.itemID, owned = true };
|
||||
}
|
||||
break;
|
||||
|
||||
case storeItemSO.ItemType.storyPassage:
|
||||
return TrySeedStoryEntry(itemSO);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private StoreOwnershipEntry TrySeedStoryEntry(storeItemSO itemSO)
|
||||
{
|
||||
var story = itemSO != null ? itemSO.associatedStoryPassage : null;
|
||||
if (story == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var entry = new StoreOwnershipEntry { storeItemId = itemSO.itemID };
|
||||
switch (itemSO.storyPassageGrantMode)
|
||||
{
|
||||
case storeItemSO.StoryPassageGrantMode.fatherOnly:
|
||||
entry.owned = story.isUnlocked;
|
||||
break;
|
||||
|
||||
case storeItemSO.StoryPassageGrantMode.specificSonOnly:
|
||||
if (HasStorySonUnlocked(story, itemSO.associatedStorySonId))
|
||||
{
|
||||
entry.unlockedStorySonIds.Add(itemSO.associatedStorySonId);
|
||||
}
|
||||
break;
|
||||
|
||||
case storeItemSO.StoryPassageGrantMode.fatherAndSpecificSon:
|
||||
if (story.isUnlocked || HasStorySonUnlocked(story, itemSO.associatedStorySonId))
|
||||
{
|
||||
entry.owned = true;
|
||||
if (itemSO.associatedStorySonId >= 0)
|
||||
{
|
||||
entry.unlockedStorySonIds.Add(itemSO.associatedStorySonId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case storeItemSO.StoryPassageGrantMode.allSons:
|
||||
if (story.isUnlocked || AreAllStorySonsUnlocked(story))
|
||||
{
|
||||
entry.owned = true;
|
||||
AddAllStorySonIds(story, entry.unlockedStorySonIds);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return entry.owned || entry.unlockedStorySonIds.Count > 0 ? entry : null;
|
||||
}
|
||||
|
||||
private void SyncAllMirrorFlags()
|
||||
{
|
||||
for (int i = 0; i < cachedStoreItems.Count; i++)
|
||||
{
|
||||
var item = cachedStoreItems[i];
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
StoreOwnershipEntry entry;
|
||||
if (!entriesByItemId.TryGetValue(item.itemID, out entry))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplyEntryToItem(item, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateGrantTarget(storeItemSO itemSO, out string failureMessage)
|
||||
{
|
||||
failureMessage = string.Empty;
|
||||
if (itemSO == null)
|
||||
{
|
||||
failureMessage = "商品数据丢失";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsOwned(itemSO))
|
||||
{
|
||||
failureMessage = "物品已解锁";
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (itemSO.itemType)
|
||||
{
|
||||
case storeItemSO.ItemType.character:
|
||||
if (itemSO.associatedAllyHero == null)
|
||||
{
|
||||
failureMessage = "角色数据未配置";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
case storeItemSO.ItemType.song:
|
||||
if (itemSO.associatedSong == null)
|
||||
{
|
||||
failureMessage = "歌曲数据未配置";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
case storeItemSO.ItemType.storyPassage:
|
||||
if (itemSO.associatedStoryPassage == null)
|
||||
{
|
||||
failureMessage = "剧情数据未配置";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (RequiresSpecificStorySon(itemSO.storyPassageGrantMode))
|
||||
{
|
||||
if (itemSO.associatedStorySonId < 0 || FindStorySonIndex(itemSO.associatedStoryPassage, itemSO.associatedStorySonId) < 0)
|
||||
{
|
||||
failureMessage = "剧情子章节未配置";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
failureMessage = "当前未配置发放逻辑";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void GrantStoryOwnership(storeItemSO itemSO, StoreOwnershipEntry entry)
|
||||
{
|
||||
if (entry.unlockedStorySonIds == null)
|
||||
{
|
||||
entry.unlockedStorySonIds = new List<int>();
|
||||
}
|
||||
|
||||
switch (itemSO.storyPassageGrantMode)
|
||||
{
|
||||
case storeItemSO.StoryPassageGrantMode.fatherOnly:
|
||||
entry.owned = true;
|
||||
break;
|
||||
|
||||
case storeItemSO.StoryPassageGrantMode.specificSonOnly:
|
||||
AddStorySonId(entry.unlockedStorySonIds, itemSO.associatedStorySonId);
|
||||
break;
|
||||
|
||||
case storeItemSO.StoryPassageGrantMode.fatherAndSpecificSon:
|
||||
entry.owned = true;
|
||||
AddStorySonId(entry.unlockedStorySonIds, itemSO.associatedStorySonId);
|
||||
break;
|
||||
|
||||
case storeItemSO.StoryPassageGrantMode.allSons:
|
||||
entry.owned = true;
|
||||
AddAllStorySonIds(itemSO.associatedStoryPassage, entry.unlockedStorySonIds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyEntryToItem(storeItemSO itemSO, StoreOwnershipEntry entry)
|
||||
{
|
||||
if (itemSO == null || entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (itemSO.itemType)
|
||||
{
|
||||
case storeItemSO.ItemType.character:
|
||||
if (entry.owned && itemSO.associatedAllyHero != null)
|
||||
{
|
||||
itemSO.associatedAllyHero.isUnlocked = true;
|
||||
MarkDirty(itemSO.associatedAllyHero);
|
||||
}
|
||||
break;
|
||||
|
||||
case storeItemSO.ItemType.song:
|
||||
if (entry.owned && itemSO.associatedSong != null)
|
||||
{
|
||||
itemSO.associatedSong.isUnlocked = true;
|
||||
MarkDirty(itemSO.associatedSong);
|
||||
}
|
||||
break;
|
||||
|
||||
case storeItemSO.ItemType.storyPassage:
|
||||
ApplyStoryEntry(itemSO.associatedStoryPassage, entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyStoryEntry(notebook_faterType story, StoreOwnershipEntry entry)
|
||||
{
|
||||
if (story == null || entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.owned || (entry.unlockedStorySonIds != null && entry.unlockedStorySonIds.Count > 0))
|
||||
{
|
||||
story.isUnlocked = true;
|
||||
MarkDirty(story);
|
||||
}
|
||||
|
||||
if (entry.unlockedStorySonIds == null || story.sonList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < entry.unlockedStorySonIds.Count; i++)
|
||||
{
|
||||
int targetSonId = entry.unlockedStorySonIds[i];
|
||||
int sonIndex = FindStorySonIndex(story, targetSonId);
|
||||
if (sonIndex < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
story.sonList[sonIndex].son_isUnlocked = true;
|
||||
MarkDirty(story);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsStoryGrantOwned(storeItemSO itemSO, StoreOwnershipEntry entry)
|
||||
{
|
||||
if (itemSO == null || itemSO.associatedStoryPassage == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (itemSO.storyPassageGrantMode)
|
||||
{
|
||||
case storeItemSO.StoryPassageGrantMode.fatherOnly:
|
||||
return entry != null && entry.owned;
|
||||
|
||||
case storeItemSO.StoryPassageGrantMode.specificSonOnly:
|
||||
return HasEntryStorySon(entry, itemSO.associatedStorySonId)
|
||||
|| HasStorySonUnlocked(itemSO.associatedStoryPassage, itemSO.associatedStorySonId);
|
||||
|
||||
case storeItemSO.StoryPassageGrantMode.fatherAndSpecificSon:
|
||||
return (entry != null && entry.owned)
|
||||
|| HasEntryStorySon(entry, itemSO.associatedStorySonId)
|
||||
|| HasStorySonUnlocked(itemSO.associatedStoryPassage, itemSO.associatedStorySonId);
|
||||
|
||||
case storeItemSO.StoryPassageGrantMode.allSons:
|
||||
return (entry != null && entry.owned) || AreAllStorySonsUnlocked(itemSO.associatedStoryPassage);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasEntryStorySon(StoreOwnershipEntry entry, int sonId)
|
||||
{
|
||||
if (entry == null || entry.unlockedStorySonIds == null || sonId < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < entry.unlockedStorySonIds.Count; i++)
|
||||
{
|
||||
if (entry.unlockedStorySonIds[i] == sonId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasStorySonUnlocked(notebook_faterType story, int sonId)
|
||||
{
|
||||
int index = FindStorySonIndex(story, sonId);
|
||||
return index >= 0 && story.sonList[index].son_isUnlocked;
|
||||
}
|
||||
|
||||
private static bool AreAllStorySonsUnlocked(notebook_faterType story)
|
||||
{
|
||||
if (story == null || story.sonList == null || story.sonList.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < story.sonList.Count; i++)
|
||||
{
|
||||
var son = story.sonList[i];
|
||||
if (son == null || !son.son_isUnlocked)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int FindStorySonIndex(notebook_faterType story, int sonId)
|
||||
{
|
||||
if (story == null || story.sonList == null || sonId < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < story.sonList.Count; i++)
|
||||
{
|
||||
var son = story.sonList[i];
|
||||
if (son != null && son.son_id == sonId)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static bool RequiresSpecificStorySon(storeItemSO.StoryPassageGrantMode mode)
|
||||
{
|
||||
return mode == storeItemSO.StoryPassageGrantMode.specificSonOnly
|
||||
|| mode == storeItemSO.StoryPassageGrantMode.fatherAndSpecificSon;
|
||||
}
|
||||
|
||||
private static void AddStorySonId(List<int> sonIds, int sonId)
|
||||
{
|
||||
if (sonIds == null || sonId < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < sonIds.Count; i++)
|
||||
{
|
||||
if (sonIds[i] == sonId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sonIds.Add(sonId);
|
||||
}
|
||||
|
||||
private static void AddAllStorySonIds(notebook_faterType story, List<int> sonIds)
|
||||
{
|
||||
if (story == null || story.sonList == null || sonIds == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < story.sonList.Count; i++)
|
||||
{
|
||||
var son = story.sonList[i];
|
||||
if (son == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AddStorySonId(sonIds, son.son_id);
|
||||
}
|
||||
}
|
||||
|
||||
private StoreOwnershipEntry GetOrCreateEntry(int storeItemId)
|
||||
{
|
||||
StoreOwnershipEntry entry;
|
||||
if (entriesByItemId.TryGetValue(storeItemId, out entry))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
entry = new StoreOwnershipEntry
|
||||
{
|
||||
storeItemId = storeItemId,
|
||||
owned = false,
|
||||
unlockedStorySonIds = new List<int>()
|
||||
};
|
||||
entriesByItemId[storeItemId] = entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
private static StoreOwnershipEntry CloneEntry(StoreOwnershipEntry source)
|
||||
{
|
||||
return new StoreOwnershipEntry
|
||||
{
|
||||
storeItemId = source.storeItemId,
|
||||
owned = source.owned,
|
||||
unlockedStorySonIds = source.unlockedStorySonIds != null
|
||||
? new List<int>(source.unlockedStorySonIds)
|
||||
: new List<int>()
|
||||
};
|
||||
}
|
||||
|
||||
private static void MarkDirty(UnityEngine.Object target)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (target != null)
|
||||
{
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user