774 lines
22 KiB
C#
774 lines
22 KiB
C#
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;
|
|
private int lastSaveFrame = -1;
|
|
|
|
[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;
|
|
}
|
|
|
|
initialized = true;
|
|
StoreOwnershipPayload payload;
|
|
bool loadedFromSave = StoreOwnershipStorage.TryLoad(out payload);
|
|
RebuildFromPayload(payload);
|
|
LoadStoreItems();
|
|
|
|
bool recoveredFromMirrors = false;
|
|
if (!loadedFromSave)
|
|
{
|
|
recoveredFromMirrors = SeedFromCurrentMirrorFlags();
|
|
}
|
|
|
|
if (loadedFromSave || recoveredFromMirrors)
|
|
{
|
|
SyncAllMirrorFlags();
|
|
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 = LocalizationService.Get("store.error.grant_logic_missing", "当前未配置发放逻辑");
|
|
return false;
|
|
}
|
|
|
|
ApplyEntryToItem(itemSO, entry);
|
|
GlobalAchievementService.EnsureInstance().RefreshDerivedMetrics();
|
|
SaveNow();
|
|
return true;
|
|
}
|
|
|
|
public bool TryRevokeOwnership(storeItemSO itemSO, out string failureMessage)
|
|
{
|
|
InitializeIfNeeded();
|
|
failureMessage = string.Empty;
|
|
|
|
if (itemSO == null)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.item_data_missing", "商品数据丢失");
|
|
return false;
|
|
}
|
|
|
|
StoreOwnershipEntry entry;
|
|
entriesByItemId.TryGetValue(itemSO.itemID, out entry);
|
|
if (entry == null && !IsOwned(itemSO))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (entry != null)
|
|
{
|
|
entry.owned = false;
|
|
if (entry.unlockedStorySonIds != null)
|
|
{
|
|
entry.unlockedStorySonIds.Clear();
|
|
}
|
|
|
|
if (!entry.owned && (entry.unlockedStorySonIds == null || entry.unlockedStorySonIds.Count == 0))
|
|
{
|
|
entriesByItemId.Remove(itemSO.itemID);
|
|
}
|
|
}
|
|
|
|
SyncAllMirrorFlags();
|
|
GlobalAchievementService.EnsureInstance().RefreshDerivedMetrics();
|
|
SaveNow();
|
|
return true;
|
|
}
|
|
|
|
public bool TryGrantOwnershipPreview(storeItemSO itemSO, out string failureMessage)
|
|
{
|
|
InitializeIfNeeded();
|
|
return ValidateGrantTarget(itemSO, out failureMessage);
|
|
}
|
|
|
|
public void SaveNow()
|
|
{
|
|
if (!initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Application.isPlaying && lastSaveFrame == Time.frameCount)
|
|
{
|
|
return;
|
|
}
|
|
|
|
lastSaveFrame = Application.isPlaying ? Time.frameCount : -1;
|
|
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 = RuntimeResourcesCache.LoadAllStoreItems();
|
|
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 bool 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;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
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()
|
|
{
|
|
ResetAllMirrorFlags();
|
|
|
|
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 void ResetAllMirrorFlags()
|
|
{
|
|
var seenHeroIds = new HashSet<int>();
|
|
var seenSongIds = new HashSet<int>();
|
|
var seenStoryIds = new HashSet<int>();
|
|
|
|
for (int i = 0; i < cachedStoreItems.Count; i++)
|
|
{
|
|
var item = cachedStoreItems[i];
|
|
if (item == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (item.itemType == storeItemSO.ItemType.character && item.associatedAllyHero != null && seenHeroIds.Add(item.associatedAllyHero.ally_heroID))
|
|
{
|
|
if (item.associatedAllyHero.isUnlocked)
|
|
{
|
|
item.associatedAllyHero.isUnlocked = false;
|
|
MarkDirty(item.associatedAllyHero);
|
|
}
|
|
}
|
|
else if (item.itemType == storeItemSO.ItemType.song && item.associatedSong != null && seenSongIds.Add(item.associatedSong.songID))
|
|
{
|
|
if (item.associatedSong.isUnlocked)
|
|
{
|
|
item.associatedSong.isUnlocked = false;
|
|
MarkDirty(item.associatedSong);
|
|
}
|
|
}
|
|
else if (item.itemType == storeItemSO.ItemType.storyPassage && item.associatedStoryPassage != null && seenStoryIds.Add(item.associatedStoryPassage.class_id))
|
|
{
|
|
var story = item.associatedStoryPassage;
|
|
bool changed = false;
|
|
|
|
if (story.isUnlocked)
|
|
{
|
|
story.isUnlocked = false;
|
|
changed = true;
|
|
}
|
|
|
|
if (story.sonList != null)
|
|
{
|
|
for (int sonIndex = 0; sonIndex < story.sonList.Count; sonIndex++)
|
|
{
|
|
var son = story.sonList[sonIndex];
|
|
if (son != null && son.son_isUnlocked)
|
|
{
|
|
son.son_isUnlocked = false;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
MarkDirty(story);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool ValidateGrantTarget(storeItemSO itemSO, out string failureMessage)
|
|
{
|
|
failureMessage = string.Empty;
|
|
if (itemSO == null)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.item_data_missing", "商品数据丢失");
|
|
return false;
|
|
}
|
|
|
|
if (IsOwned(itemSO))
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.item_already_unlocked", "物品已解锁");
|
|
return false;
|
|
}
|
|
|
|
switch (itemSO.itemType)
|
|
{
|
|
case storeItemSO.ItemType.character:
|
|
if (itemSO.associatedAllyHero == null)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.character_data_missing", "角色数据未配置");
|
|
return false;
|
|
}
|
|
return true;
|
|
|
|
case storeItemSO.ItemType.song:
|
|
if (itemSO.associatedSong == null)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.song_data_missing", "歌曲数据未配置");
|
|
return false;
|
|
}
|
|
return true;
|
|
|
|
case storeItemSO.ItemType.storyPassage:
|
|
if (itemSO.associatedStoryPassage == null)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.story_data_missing", "剧情数据未配置");
|
|
return false;
|
|
}
|
|
|
|
if (RequiresSpecificStorySon(itemSO.storyPassageGrantMode))
|
|
{
|
|
if (itemSO.associatedStorySonId < 0 || FindStorySonIndex(itemSO.associatedStoryPassage, itemSO.associatedStorySonId) < 0)
|
|
{
|
|
failureMessage = LocalizationService.Get("store.error.story_chapter_missing", "剧情子章节未配置");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
|
|
default:
|
|
failureMessage = LocalizationService.Get("store.error.grant_logic_missing", "当前未配置发放逻辑");
|
|
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;
|
|
itemSO.associatedAllyHero.SetJoinDateIfMissing();
|
|
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 (!Application.isPlaying && target != null)
|
|
{
|
|
EditorUtility.SetDirty(target);
|
|
}
|
|
#endif
|
|
}
|
|
}
|