Files
bansonic_beta_main/Assets/storeSystem/storeSystem.cs
T

1032 lines
28 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using Bansonic;
using UnityEngine;
using UnityEngine.UI;
public class storeSystem : MonoBehaviour
{
[Header("Player Data")]
[SerializeField] private Player_SO playerData;
[Header("list toggles")]
public ToggleGroup toggleGroup;
public Toggle allItems_toggle;
public Toggle recentlyHot_toggle;
public Toggle idols_toggle;
public Toggle songs_toggle;
public Toggle storyPassage_toggle;
public Toggle consumable_toggle;
public Toggle otherType_toggle;
[Header("objects")]
public GameObject itemPrefab;
public Transform contentParent;
[Header("paths")]
[SerializeField] private string editorStoreItemPath;
[SerializeField] private string runtimeStoreItemPath;
[Header("sprites")]
public Sprite coinSprite;
public Sprite cannotBuySprite;
[Header("selecting item purchase")]
public Text p_itemName;
public Image p_itemImage;
public Image p_sumCostImage;
public Text p_sumCostText;
public Button p_iAmount_plus;
public Button p_iAmount_minus;
public InputField p_iAmount_input;
public Button purchaseButton;
public Text p_detailedDescriptionText;
private const int MinPurchaseAmount = 1;
private const int MaxPurchaseAmount = 999999;
private readonly List<storeItemSO> cachedItems = new List<storeItemSO>();
private readonly List<Toggle> toggles = new List<Toggle>();
private StoreRuntimeSaveData persistedState = new StoreRuntimeSaveData();
private storeItemSO currentSelectedItem;
private int currentSelectedItemId = -1;
private int currentPurchaseAmount = MinPurchaseAmount;
private bool suppressAmountInputCallback;
private string SaveFilePath
{
get { return Path.Combine(Application.persistentDataPath, "storeSystem_state.json"); }
}
private void Awake()
{
CacheToggles();
RegisterToggleCallbacks();
RegisterPurchasePanelCallbacks();
}
private void Start()
{
ResetPriceImageObjects();
SetDefaultToggleState();
LoadPersistedState();
LoadAllStoreItems();
ApplyPersistedStateToItems();
RefreshCurrentView();
}
private void OnDestroy()
{
UnregisterToggleCallbacks();
UnregisterPurchasePanelCallbacks();
}
private void CacheToggles()
{
toggles.Clear();
AddToggle(allItems_toggle);
AddToggle(recentlyHot_toggle);
AddToggle(idols_toggle);
AddToggle(songs_toggle);
AddToggle(storyPassage_toggle);
AddToggle(consumable_toggle);
AddToggle(otherType_toggle);
}
private void AddToggle(Toggle toggle)
{
if (toggle != null && !toggles.Contains(toggle))
{
toggles.Add(toggle);
}
}
private void RegisterToggleCallbacks()
{
for (int i = 0; i < toggles.Count; i++)
{
toggles[i].onValueChanged.AddListener(OnFilterToggleValueChanged);
}
}
private void UnregisterToggleCallbacks()
{
for (int i = 0; i < toggles.Count; i++)
{
if (toggles[i] != null)
{
toggles[i].onValueChanged.RemoveListener(OnFilterToggleValueChanged);
}
}
}
private void RegisterPurchasePanelCallbacks()
{
if (p_iAmount_plus != null)
{
p_iAmount_plus.onClick.AddListener(OnPurchaseAmountPlusClicked);
}
if (p_iAmount_minus != null)
{
p_iAmount_minus.onClick.AddListener(OnPurchaseAmountMinusClicked);
}
if (p_iAmount_input != null)
{
p_iAmount_input.onEndEdit.AddListener(OnPurchaseAmountInputEndEdit);
}
}
private void UnregisterPurchasePanelCallbacks()
{
if (p_iAmount_plus != null)
{
p_iAmount_plus.onClick.RemoveListener(OnPurchaseAmountPlusClicked);
}
if (p_iAmount_minus != null)
{
p_iAmount_minus.onClick.RemoveListener(OnPurchaseAmountMinusClicked);
}
if (p_iAmount_input != null)
{
p_iAmount_input.onEndEdit.RemoveListener(OnPurchaseAmountInputEndEdit);
}
}
private void OnFilterToggleValueChanged(bool _)
{
currentSelectedItem = null;
currentSelectedItemId = -1;
currentPurchaseAmount = MinPurchaseAmount;
RefreshCurrentView();
}
private void SetDefaultToggleState()
{
if (allItems_toggle == null)
{
return;
}
for (int i = 0; i < toggles.Count; i++)
{
if (toggles[i] != null && toggles[i] != allItems_toggle)
{
toggles[i].SetIsOnWithoutNotify(false);
}
}
allItems_toggle.SetIsOnWithoutNotify(true);
}
private void LoadAllStoreItems()
{
cachedItems.Clear();
var uniqueItemIds = new HashSet<int>();
#if UNITY_EDITOR
if (!string.IsNullOrEmpty(editorStoreItemPath))
{
var guids = UnityEditor.AssetDatabase.FindAssets("t:storeItemSO", new[] { editorStoreItemPath });
for (int i = 0; i < guids.Length; i++)
{
var assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
var itemSO = UnityEditor.AssetDatabase.LoadAssetAtPath<storeItemSO>(assetPath);
TryCacheStoreItem(itemSO, assetPath, uniqueItemIds);
}
}
#endif
if (!string.IsNullOrEmpty(runtimeStoreItemPath))
{
var runtimeItems = Resources.LoadAll<storeItemSO>(runtimeStoreItemPath);
for (int i = 0; i < runtimeItems.Length; i++)
{
var itemSO = runtimeItems[i];
var sourceLabel = string.IsNullOrEmpty(itemSO != null ? itemSO.name : string.Empty)
? runtimeStoreItemPath
: runtimeStoreItemPath + "/" + itemSO.name;
TryCacheStoreItem(itemSO, sourceLabel, uniqueItemIds);
}
}
}
private void TryCacheStoreItem(storeItemSO itemSO, string sourceLabel, HashSet<int> uniqueItemIds)
{
if (itemSO == null)
{
return;
}
if (!uniqueItemIds.Add(itemSO.itemID))
{
Debug.LogWarning($"storeSystem: duplicate itemID {itemSO.itemID} found at '{sourceLabel}'. Duplicate item was skipped.", itemSO);
return;
}
cachedItems.Add(itemSO);
}
private void ApplyPersistedStateToItems()
{
bool stateChanged = false;
for (int i = 0; i < cachedItems.Count; i++)
{
var itemSO = cachedItems[i];
StoreRuntimeEntry savedEntry;
if (!TryGetEntry(itemSO.itemID, out savedEntry))
{
#if UNITY_EDITOR
if (itemSO.user_has_read || itemSO.purchasedCount > 0)
{
savedEntry = GetOrCreateEntry(itemSO.itemID);
savedEntry.userHasRead = itemSO.user_has_read;
savedEntry.purchasedCount = itemSO.purchasedCount;
stateChanged = true;
}
#endif
continue;
}
#if UNITY_EDITOR
bool editorHasRead = itemSO.user_has_read;
int editorPurchasedCount = itemSO.purchasedCount;
if (savedEntry.userHasRead != editorHasRead || savedEntry.purchasedCount != editorPurchasedCount)
{
savedEntry.userHasRead = editorHasRead;
savedEntry.purchasedCount = editorPurchasedCount;
stateChanged = true;
}
#else
bool mergedHasRead = itemSO.user_has_read || savedEntry.userHasRead;
int mergedPurchasedCount = Mathf.Max(itemSO.purchasedCount, savedEntry.purchasedCount);
itemSO.user_has_read = mergedHasRead;
itemSO.purchasedCount = mergedPurchasedCount;
if (savedEntry.userHasRead != mergedHasRead || savedEntry.purchasedCount != mergedPurchasedCount)
{
savedEntry.userHasRead = mergedHasRead;
savedEntry.purchasedCount = mergedPurchasedCount;
stateChanged = true;
}
#endif
}
if (stateChanged)
{
SavePersistedState();
}
}
public void RefreshCurrentView()
{
if (itemPrefab == null || contentParent == null)
{
ClearPurchasePanel();
return;
}
ClearSpawnedItems();
var visibleItems = new List<storeItemSO>();
for (int i = 0; i < cachedItems.Count; i++)
{
var itemSO = cachedItems[i];
if (!ShouldDisplay(itemSO))
{
continue;
}
visibleItems.Add(itemSO);
SpawnItem(itemSO);
}
ResolveVisibleSelection(visibleItems);
}
private bool ShouldDisplay(storeItemSO itemSO)
{
if (itemSO == null)
{
return false;
}
if (!itemSO.isOnShelf)
{
return false;
}
if (recentlyHot_toggle != null && recentlyHot_toggle.isOn)
{
return itemSO.isHot;
}
if (idols_toggle != null && idols_toggle.isOn)
{
return itemSO.itemType == storeItemSO.ItemType.character;
}
if (songs_toggle != null && songs_toggle.isOn)
{
return itemSO.itemType == storeItemSO.ItemType.song;
}
if (storyPassage_toggle != null && storyPassage_toggle.isOn)
{
return itemSO.itemType == storeItemSO.ItemType.storyPassage;
}
if (consumable_toggle != null && consumable_toggle.isOn)
{
return itemSO.itemType == storeItemSO.ItemType.consumable;
}
if (otherType_toggle != null && otherType_toggle.isOn)
{
return itemSO.itemType == storeItemSO.ItemType.otherType;
}
return allItems_toggle == null || allItems_toggle.isOn;
}
private void SpawnItem(storeItemSO itemSO)
{
var instance = Instantiate(itemPrefab, contentParent);
var itemView = instance.GetComponent<storeItemPrefab>();
if (itemView == null)
{
Debug.LogWarning("storeSystem: itemPrefab missing storeItemPrefab component.", instance);
Destroy(instance);
return;
}
itemView.thisItemSO = itemSO;
itemView.onItemClicked = HandleItemClicked;
var button = instance.GetComponent<Button>();
if (button != null)
{
button.onClick.AddListener(itemView.NotifyClicked);
}
if (itemView.thisItem_iconImage != null)
{
itemView.thisItem_iconImage.sprite = itemSO.itemIcon;
}
if (itemView.thisItem_nameText != null)
{
itemView.thisItem_nameText.text = itemSO.itemName;
}
itemView.SetDescriptionContent(itemSO.itemName, itemSO.itemDescription);
if (itemView.thisItem_amountAndLimitationText != null)
{
itemView.thisItem_amountAndLimitationText.text = GetAmountAndQuotaText(itemSO);
}
SetupPrice(itemView, itemSO);
SetupAvailabilityState(itemView, itemSO);
SetupReadState(itemView, itemSO);
}
private void ResolveVisibleSelection(List<storeItemSO> visibleItems)
{
if (visibleItems == null || visibleItems.Count == 0)
{
currentSelectedItem = null;
currentSelectedItemId = -1;
currentPurchaseAmount = MinPurchaseAmount;
ClearPurchasePanel();
return;
}
storeItemSO itemToSelect = null;
if (currentSelectedItemId >= 0)
{
for (int i = 0; i < visibleItems.Count; i++)
{
if (visibleItems[i].itemID == currentSelectedItemId)
{
itemToSelect = visibleItems[i];
break;
}
}
}
bool resetAmount = false;
if (itemToSelect == null)
{
itemToSelect = visibleItems[0];
resetAmount = true;
}
ApplySelectedItem(itemToSelect, resetAmount);
}
private void ApplySelectedItem(storeItemSO itemSO, bool resetAmount)
{
currentSelectedItem = itemSO;
currentSelectedItemId = itemSO != null ? itemSO.itemID : -1;
if (itemSO == null)
{
ClearPurchasePanel();
return;
}
if (p_itemName != null)
{
p_itemName.text = itemSO.itemName ?? string.Empty;
}
if (p_itemImage != null)
{
p_itemImage.sprite = itemSO.itemIcon;
p_itemImage.enabled = itemSO.itemIcon != null;
}
if (p_detailedDescriptionText != null)
{
p_detailedDescriptionText.text = string.IsNullOrWhiteSpace(itemSO.itemDetailedDescription)
? "暂无描述信息"
: itemSO.itemDetailedDescription;
}
if (resetAmount)
{
SetPurchaseAmount(MinPurchaseAmount, false);
}
else
{
UpdatePurchaseAmountInput();
RefreshPurchaseSummary();
}
RefreshPurchaseButtonState();
}
private void ClearPurchasePanel()
{
if (p_itemName != null)
{
p_itemName.text = string.Empty;
}
if (p_itemImage != null)
{
p_itemImage.sprite = null;
p_itemImage.enabled = false;
}
if (p_detailedDescriptionText != null)
{
p_detailedDescriptionText.text = string.Empty;
}
if (p_sumCostText != null)
{
p_sumCostText.text = string.Empty;
}
SetPriceImageState(p_sumCostImage, null, false);
currentPurchaseAmount = MinPurchaseAmount;
UpdatePurchaseAmountInput();
RefreshPurchaseButtonState();
}
private void OnPurchaseAmountPlusClicked()
{
SetPurchaseAmount(currentPurchaseAmount + 1, true);
}
private void OnPurchaseAmountMinusClicked()
{
SetPurchaseAmount(currentPurchaseAmount - 1, true);
}
private void OnPurchaseAmountInputEndEdit(string value)
{
if (suppressAmountInputCallback)
{
return;
}
long parsedValue;
if (!long.TryParse((value ?? string.Empty).Trim(), out parsedValue))
{
parsedValue = MinPurchaseAmount;
}
int safeValue;
if (parsedValue > int.MaxValue)
{
safeValue = int.MaxValue;
}
else if (parsedValue < int.MinValue)
{
safeValue = int.MinValue;
}
else
{
safeValue = (int)parsedValue;
}
SetPurchaseAmount(safeValue, true);
}
private void SetPurchaseAmount(int amount, bool showWarnings)
{
int clampedAmount = amount;
if (amount > MaxPurchaseAmount)
{
clampedAmount = MaxPurchaseAmount;
if (showWarnings)
{
gNotice.warning.display("达到单次购买限额");
}
}
else if (amount < MinPurchaseAmount)
{
clampedAmount = MinPurchaseAmount;
if (showWarnings)
{
gNotice.warning.display("本店禁止无实物交易");
}
}
currentPurchaseAmount = clampedAmount;
UpdatePurchaseAmountInput();
RefreshPurchaseSummary();
}
private void UpdatePurchaseAmountInput()
{
if (p_iAmount_input == null)
{
return;
}
suppressAmountInputCallback = true;
p_iAmount_input.text = currentPurchaseAmount.ToString();
suppressAmountInputCallback = false;
}
private void RefreshPurchaseSummary()
{
ResetPriceImageObjectState(p_sumCostImage);
if (currentSelectedItem == null)
{
if (p_sumCostText != null)
{
p_sumCostText.text = string.Empty;
}
SetPriceImageState(p_sumCostImage, null, false);
RefreshPurchaseButtonState();
return;
}
if (!CanPurchaseCurrentSelection(currentSelectedItem))
{
if (p_sumCostText != null)
{
p_sumCostText.text = "不可购买";
}
SetCannotBuyImageState(p_sumCostImage);
RefreshPurchaseButtonState();
return;
}
var cost = currentSelectedItem.costRequirements[0];
long totalCost = (long)cost.amount * currentPurchaseAmount;
if (p_sumCostText != null)
{
p_sumCostText.text = totalCost.ToString();
}
SetPriceImageState(p_sumCostImage, GetCurrencySprite(cost.currencyType), true);
RefreshPurchaseButtonState();
}
private Sprite GetCurrencySprite(storeItemSO.CurrencyType currencyType)
{
if (currencyType == storeItemSO.CurrencyType.coins)
{
return coinSprite;
}
return null;
}
private bool HasCoinRequirement(storeItemSO itemSO)
{
if (itemSO == null || itemSO.costRequirements == null || itemSO.costRequirements.Count == 0)
{
return false;
}
return GetCurrencySprite(itemSO.costRequirements[0].currencyType) != null;
}
private void SetPriceImageState(Image priceImage, Sprite sprite, bool shouldShow)
{
if (priceImage == null)
{
return;
}
priceImage.gameObject.SetActive(shouldShow);
if (!shouldShow)
{
priceImage.sprite = null;
priceImage.enabled = false;
return;
}
priceImage.sprite = sprite;
priceImage.enabled = sprite != null;
}
private void SetCannotBuyImageState(Image priceImage)
{
if (priceImage == null)
{
return;
}
if (cannotBuySprite == null)
{
SetPriceImageState(priceImage, null, false);
return;
}
SetPriceImageState(priceImage, cannotBuySprite, true);
}
private void ResetPriceImageObjects()
{
ResetPriceImageObjectState(p_sumCostImage);
}
private void ResetPriceImageObjectState(Image priceImage)
{
if (priceImage == null)
{
return;
}
priceImage.gameObject.SetActive(true);
priceImage.sprite = null;
priceImage.enabled = false;
}
private void RefreshPurchaseButtonState()
{
if (purchaseButton == null)
{
return;
}
purchaseButton.interactable = CanPurchaseCurrentSelection(currentSelectedItem);
}
private string GetAmountAndQuotaText(storeItemSO itemSO)
{
string prefix = itemSO.itemSinglePurchaseQty == 1 ? string.Empty : $"{itemSO.itemSinglePurchaseQty}个装/";
if (itemSO.itemPurchaseQuota < 0)
{
return prefix + "不限购";
}
return $"{prefix}限购{itemSO.purchasedCount}/{itemSO.itemPurchaseQuota}个";
}
private void SetupPrice(storeItemPrefab itemView, storeItemSO itemSO)
{
if (itemView.thisItem_priceText != null)
{
itemView.thisItem_priceText.text = GetPriceAmountText(itemSO);
}
if (itemView.thisPrice_iconImage == null)
{
return;
}
ResetPriceImageObjectState(itemView.thisPrice_iconImage);
if (!ShouldShowPriceIcon(itemSO))
{
SetCannotBuyImageState(itemView.thisPrice_iconImage);
return;
}
var cost = itemSO.costRequirements[0];
SetPriceImageState(itemView.thisPrice_iconImage, GetCurrencySprite(cost.currencyType), true);
}
private string GetPriceAmountText(storeItemSO itemSO)
{
if (itemSO == null || !itemSO.canbepurchased || !HasCoinRequirement(itemSO))
{
return "不可购买";
}
return itemSO.costRequirements[0].amount.ToString();
}
private bool ShouldShowPriceIcon(storeItemSO itemSO)
{
return itemSO != null && itemSO.canbepurchased && HasCoinRequirement(itemSO);
}
private bool CanPurchaseCurrentSelection(storeItemSO itemSO)
{
return itemSO != null
&& itemSO.canbepurchased
&& !IsSoldOut(itemSO)
&& HasCoinRequirement(itemSO);
}
private void SetupAvailabilityState(storeItemPrefab itemView, storeItemSO itemSO)
{
bool isSoldOut = IsSoldOut(itemSO);
bool cannotPurchase = !itemSO.canbepurchased;
if (itemView.leftCorner_statusImage != null)
{
itemView.leftCorner_statusImage.SetActive(isSoldOut);
}
if (itemView.lock_cannotClickImage != null)
{
itemView.lock_cannotClickImage.SetActive(isSoldOut || cannotPurchase);
}
if (itemView.why_cannot_buy != null)
{
if (isSoldOut)
{
itemView.why_cannot_buy.text = "已售罄";
}
else if (cannotPurchase)
{
itemView.why_cannot_buy.text = "物品未上架";
}
else
{
itemView.why_cannot_buy.text = string.Empty;
}
}
}
private void SetupReadState(storeItemPrefab itemView, storeItemSO itemSO)
{
if (itemView.rightCorner_statusImage != null)
{
itemView.rightCorner_statusImage.SetActive(!itemSO.user_has_read);
}
}
private bool IsSoldOut(storeItemSO itemSO)
{
if (itemSO == null || itemSO.itemPurchaseQuota < 0)
{
return false;
}
return itemSO.purchasedCount >= itemSO.itemPurchaseQuota;
}
private void HandleItemClicked(storeItemPrefab itemView)
{
if (itemView == null || itemView.thisItemSO == null)
{
return;
}
currentSelectedItemId = itemView.thisItemSO.itemID;
ApplySelectedItem(itemView.thisItemSO, true);
if (!itemView.thisItemSO.user_has_read)
{
itemView.thisItemSO.user_has_read = true;
var entry = GetOrCreateEntry(itemView.thisItemSO.itemID);
entry.userHasRead = true;
entry.purchasedCount = itemView.thisItemSO.purchasedCount;
SavePersistedState();
RefreshCurrentView();
}
}
public void RegisterPurchasedCount(int itemID, int purchasedAmount)
{
if (purchasedAmount <= 0)
{
return;
}
for (int i = 0; i < cachedItems.Count; i++)
{
var itemSO = cachedItems[i];
if (itemSO.itemID != itemID)
{
continue;
}
itemSO.purchasedCount += purchasedAmount;
var entry = GetOrCreateEntry(itemID);
entry.userHasRead = itemSO.user_has_read;
entry.purchasedCount = itemSO.purchasedCount;
SavePersistedState();
RefreshCurrentView();
return;
}
}
private bool TryGetEntry(int itemID, out StoreRuntimeEntry entry)
{
entry = null;
if (persistedState.entries == null)
{
persistedState.entries = new List<StoreRuntimeEntry>();
return false;
}
for (int i = 0; i < persistedState.entries.Count; i++)
{
if (persistedState.entries[i] != null && persistedState.entries[i].itemID == itemID)
{
entry = persistedState.entries[i];
return true;
}
}
return false;
}
private StoreRuntimeEntry GetOrCreateEntry(int itemID)
{
StoreRuntimeEntry entry;
if (TryGetEntry(itemID, out entry))
{
return entry;
}
if (persistedState.entries == null)
{
persistedState.entries = new List<StoreRuntimeEntry>();
}
entry = new StoreRuntimeEntry
{
itemID = itemID,
userHasRead = false,
purchasedCount = 0
};
persistedState.entries.Add(entry);
return entry;
}
private void LoadPersistedState()
{
persistedState = new StoreRuntimeSaveData();
if (!File.Exists(SaveFilePath))
{
return;
}
try
{
var json = File.ReadAllText(SaveFilePath);
var loadedData = JsonUtility.FromJson<StoreRuntimeSaveData>(json);
if (loadedData != null && loadedData.entries != null)
{
persistedState = loadedData;
NormalizePersistedEntries();
}
}
catch (Exception ex)
{
Debug.LogWarning($"storeSystem: failed to load persisted state. {ex.Message}");
persistedState = new StoreRuntimeSaveData();
}
}
private void NormalizePersistedEntries()
{
if (persistedState.entries == null)
{
persistedState.entries = new List<StoreRuntimeEntry>();
return;
}
var normalizedEntries = new List<StoreRuntimeEntry>();
var indexByItemId = new Dictionary<int, int>();
bool hadDuplicates = false;
for (int i = 0; i < persistedState.entries.Count; i++)
{
var source = persistedState.entries[i];
if (source == null)
{
continue;
}
int existingIndex;
if (indexByItemId.TryGetValue(source.itemID, out existingIndex))
{
var target = normalizedEntries[existingIndex];
target.userHasRead = target.userHasRead || source.userHasRead;
target.purchasedCount = Mathf.Max(target.purchasedCount, source.purchasedCount);
hadDuplicates = true;
continue;
}
var clone = new StoreRuntimeEntry
{
itemID = source.itemID,
userHasRead = source.userHasRead,
purchasedCount = source.purchasedCount
};
indexByItemId.Add(clone.itemID, normalizedEntries.Count);
normalizedEntries.Add(clone);
}
persistedState.entries = normalizedEntries;
if (hadDuplicates)
{
SavePersistedState();
}
}
private void SavePersistedState()
{
try
{
NormalizePersistedEntries();
var json = JsonUtility.ToJson(persistedState, true);
File.WriteAllText(SaveFilePath, json);
#if UNITY_EDITOR
PersistStateBackToEditorAssets();
#endif
}
catch (Exception ex)
{
Debug.LogWarning($"storeSystem: failed to save persisted state. {ex.Message}");
}
}
#if UNITY_EDITOR
private void PersistStateBackToEditorAssets()
{
for (int i = 0; i < cachedItems.Count; i++)
{
UnityEditor.EditorUtility.SetDirty(cachedItems[i]);
}
UnityEditor.AssetDatabase.SaveAssets();
}
#endif
private void ClearSpawnedItems()
{
for (int i = contentParent.childCount - 1; i >= 0; i--)
{
Destroy(contentParent.GetChild(i).gameObject);
}
}
[Serializable]
private class StoreRuntimeSaveData
{
public List<StoreRuntimeEntry> entries = new List<StoreRuntimeEntry>();
}
[Serializable]
private class StoreRuntimeEntry
{
public int itemID;
public bool userHasRead;
public int purchasedCount;
}
}