using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; using BrewMonster.Network; public class ShopUIManager : MonoBehaviour { [Header("Shop Data")] public GShopLoader shopLoader; [Header("UI Panels")] public GameObject shopMainPanel; public GameObject shopDetailPanel; public GameObject subTypeShopPanel; [Header("Item Display")] public Transform itemContainer; // Parent for item panels public GameObject itemPanelPrefab; // Prefab for individual item panels [Header("Object Pooling")] public ItemPanelPool itemPanelPool; // Object pool for item panels public bool useObjectPooling = true; [Header("Category Buttons")] public Button[] categoryButtons; // 6 buttons for categories [Header("Detail Panel Components")] public TextMeshProUGUI detailItemName; public TextMeshProUGUI detailItemDescription; public Image detailItemIcon; public TextMeshProUGUI detailItemPrice; public TextMeshProUGUI detailItemQuantity; public Button buyButton; public Button closeDetailButton; public ScrollRect detailScrollRect; [Header("Main Panel Components")] public Button closeShopButton; [Header("Cash UI (assign any text fields to mirror cash amount)")] [SerializeField] private List cashTextsLegacy = new List(); [SerializeField] private List cashTextsTMP = new List(); // Keep last known cash so newly opened shops can render immediately. private static bool s_hasPendingCash; private static int s_pendingCashAmount; private List currentItemPanels = new List(); private GShopItem selectedItem; private int currentCategory = 0; // 0-5 for the 6 merged categories [Header("Sub Type Shop")] //-1 means all sub types public SubTypeShop subTypeShop; private int currentSubType = -1; private bool _shopLoaderEventsSubscribed; void Start() { InitializeUI(); SetupEventListeners(); InitializePool(); SubscribeShopLoaderEvents(); } void SubscribeShopLoaderEvents() { if (_shopLoaderEventsSubscribed) return; if (shopLoader == null) shopLoader = FindFirstObjectByType(); if (shopLoader == null) return; shopLoader.OnPrimaryShopLoaded += OnPrimaryShopDataReady; _shopLoaderEventsSubscribed = true; // Load may have finished before we subscribed (e.g. script order). if (shopLoader.IsPrimaryShopLoaded) OnPrimaryShopDataReady(); } void OnPrimaryShopDataReady() { if (shopMainPanel != null && shopMainPanel.activeSelf) RefreshShopDisplay(); } void InitializePool() { if (useObjectPooling && itemPanelPool == null) { // Auto-create pool if not assigned GameObject poolObj = new GameObject("ItemPanelPool"); poolObj.transform.SetParent(transform); itemPanelPool = poolObj.AddComponent(); itemPanelPool.itemPanelPrefab = itemPanelPrefab; } } void InitializeUI() { // Initially hide panels if (shopMainPanel != null) shopMainPanel.SetActive(false); if (shopDetailPanel != null) shopDetailPanel.SetActive(false); if (subTypeShop != null) subTypeShopPanel.SetActive(false); } void SetupEventListeners() { // Setup category buttons for (int i = 0; i < categoryButtons.Length && i < 6; i++) { int categoryIndex = i; // Capture for closure categoryButtons[i].onClick.AddListener(() => OnCategorySelected(categoryIndex)); } // Setup detail panel buttons if (buyButton != null) buyButton.onClick.AddListener(OnBuyItem); if (closeDetailButton != null) closeDetailButton.onClick.AddListener(CloseDetailPanel); // Setup main panel close button if (closeShopButton != null) closeShopButton.onClick.AddListener(CloseShop); } public void OpenShop() { if (shopMainPanel != null) { SubscribeShopLoaderEvents(); shopMainPanel.SetActive(true); // Always apply category 0 when opening; OnCategorySelected(0) no-ops if currentCategory is already 0. OnCategorySelected(0, forceRefresh: true); ApplyPendingCash(); UnityGameSession.RequesrQueryPlayerCash(); } } public void UpdateCash(int amount) { string text = amount.ToString(); int major = amount / 100; // e.g. 49780 -> 497 (KNB) int minor = amount % 100; // e.g. 49780 -> 80 (NNB) string textMajor = major.ToString(); string textMinor = minor.ToString(); // For now only show the first part (major = 497) if (cashTextsLegacy != null) { for (int i = 0; i < cashTextsLegacy.Count; i++) { var t = cashTextsLegacy[i]; if (t != null) t.text = text; if (t != null) t.text = textMajor; } } if (cashTextsTMP != null) for (int i = 0; i < cashTextsTMP.Count; i++) { var t = cashTextsTMP[i]; if (t != null) t.text = text; if (t != null) t.text = textMajor; } } public static void CacheCash(int amount) { s_pendingCashAmount = amount; s_hasPendingCash = true; var all = Resources.FindObjectsOfTypeAll(); if (all != null) { for (int i = 0; i < all.Length; i++) { var ui = all[i]; if (ui != null && ui.gameObject.scene.IsValid()) { ui.ApplyPendingCash(); } } } } private void ApplyPendingCash() { if (s_hasPendingCash) { UpdateCash(s_pendingCashAmount); } } public void CloseShop() { if (shopMainPanel != null) shopMainPanel.SetActive(false); if (shopDetailPanel != null) shopDetailPanel.SetActive(false); } void OnCategorySelected(int categoryIndex, bool forceRefresh = false) { if (!forceRefresh && categoryIndex == currentCategory) return; float startTime = Time.realtimeSinceStartup; currentCategory = categoryIndex; RefreshShopDisplay(); // Update button states for (int i = 0; i < categoryButtons.Length; i++) { if (categoryButtons[i] != null) { categoryButtons[i].interactable = (i != categoryIndex); } } // Log performance float switchTime = Time.realtimeSinceStartup - startTime; Debug.Log($"Category switch to {categoryIndex} completed in {switchTime * 1000f:F2}ms"); } // Allow external components (e.g., ShopCategoryManager) to switch category public void SetCategoryIndex(int categoryIndex) { OnCategorySelected(categoryIndex); } public void RefreshShopDisplay() { // Return all current panels to pool ReturnAllPanelsToPool(); if (shopLoader == null || shopLoader.primaryShop == null) { Debug.LogWarning("ShopLoader or primary shop data not available"); return; } // Get items for current category List categoryItems = GetItemsForCategory(currentCategory); // Create item panels using pooling int siblingIndexCounter = 0; foreach (GShopItem item in categoryItems) { CreateItemPanelFromPool(item, siblingIndexCounter); siblingIndexCounter++; } List subTypeNames = GetSubTypeNamesForCategory(currentCategory); currentSubType = -1; subTypeShopPanel.SetActive(subTypeNames.Count > 0); if (subTypeShop != null) subTypeShop.GenerateSubTypeShop(subTypeNames, this); } public void SetSubTypeFilter(int subTypeIndex) { currentSubType = subTypeIndex; ApplySubTypeFilter(); } public void SetSubTypeFilterByName(string subTypeName) { int subTypeIndex = GetSubTypeIndexFromName(subTypeName); SetSubTypeFilter(subTypeIndex); } public void ApplySubTypeFilter() { ReturnAllPanelsToPool(); if (shopLoader == null || shopLoader.primaryShop == null) { Debug.LogWarning("ShopLoader or primary shop data not available"); return; } if(currentSubType == -1) { RefreshShopDisplay(); return; } List categoryItems = GetItemsForCategory(currentCategory, currentSubType); int siblingIndexCounter = 0; foreach (GShopItem item in categoryItems) { CreateItemPanelFromPool(item, siblingIndexCounter); siblingIndexCounter++; } } List GetItemsForCategory(int categoryIndex, int subTypeIndex=-1) { List items = new List(); if (shopLoader.primaryShop.items == null) return items; foreach (GShopItem item in shopLoader.primaryShop.items) { if (IsItemInCategory(item, categoryIndex)) { if(IsItemInSubType(item, subTypeIndex)) { items.Add(item); } } } return items; } List GetSubTypeNamesForCategory(int categoryIndex) { List subTypeNames = new List(); if (shopLoader?.primaryShop?.mainTypes == null) return subTypeNames; // Use the same mapping as IsItemInCategory: 0=0, 1=2, 2=5, 3=1+3+4, 4=6, 5=7 List mainTypeIndices = new List(); switch (categoryIndex) { case 0: mainTypeIndices.Add(0); break; case 1: mainTypeIndices.Add(2); break; case 2: mainTypeIndices.Add(5); break; case 3: mainTypeIndices.Add(1); mainTypeIndices.Add(3); mainTypeIndices.Add(4); break; case 4: mainTypeIndices.Add(6); break; case 5: mainTypeIndices.Add(7); break; default: break; } foreach (int mainTypeIndex in mainTypeIndices) { if (mainTypeIndex >= 0 && mainTypeIndex < shopLoader.primaryShop.mainTypes.Count) { foreach (string subType in shopLoader.primaryShop.mainTypes[mainTypeIndex].subTypes) { if (!subTypeNames.Contains(subType)) { subTypeNames.Add(subType); } } } } return subTypeNames; } bool IsItemInCategory(GShopItem item, int categoryIndex) { // Category mapping: 0=1, 1=2, 2=1+3+4, 3=6, 4=7, 5=8 switch (categoryIndex) { case 0: return item.mainType == 0; // Category 1 case 1: return item.mainType == 2; // Category 2 case 2: return item.mainType == 5; // Categories 1, 3, 4 merged case 3: return item.mainType == 1 || item.mainType == 3 || item.mainType == 4; // Category 6 case 4: return item.mainType == 6; // Category 7 case 5: return item.mainType == 7; // Category 8 default: return false; } } bool IsItemInSubType(GShopItem item, int combinedSubTypeIndex) { if(combinedSubTypeIndex == -1) return true; // Get the subType name for this item's mainType and subType index if (shopLoader?.primaryShop?.mainTypes == null) return false; if (item.mainType < 0 || item.mainType >= shopLoader.primaryShop.mainTypes.Count) return false; var mainType = shopLoader.primaryShop.mainTypes[item.mainType]; if (item.subType < 0 || item.subType >= mainType.subTypes.Count) return false; string itemSubTypeName = mainType.subTypes[item.subType]; // Get the subType name for the filter index string filterSubTypeName = GetSubTypeNameFromIndex(combinedSubTypeIndex); return itemSubTypeName == filterSubTypeName; } int GetSubTypeIndexFromName(string subTypeName) { if (shopLoader?.primaryShop?.mainTypes == null || string.IsNullOrEmpty(subTypeName)) return -1; // Find the subType name in the current category's mainTypes List mainTypeIndices = new List(); switch (currentCategory) { case 0: mainTypeIndices.Add(0); break; case 1: mainTypeIndices.Add(2); break; case 2: mainTypeIndices.Add(5); break; case 3: mainTypeIndices.Add(1); mainTypeIndices.Add(3); mainTypeIndices.Add(4); break; case 4: mainTypeIndices.Add(6); break; case 5: mainTypeIndices.Add(7); break; default: break; } // Find the first matching subType across all relevant mainTypes // We use a combined index: mainTypeIndex * 1000 + subTypeIndex // This allows us to uniquely identify subTypes across different mainTypes foreach (int mainTypeIndex in mainTypeIndices) { if (mainTypeIndex >= 0 && mainTypeIndex < shopLoader.primaryShop.mainTypes.Count) { var mainType = shopLoader.primaryShop.mainTypes[mainTypeIndex]; for (int i = 0; i < mainType.subTypes.Count; i++) { if (mainType.subTypes[i] == subTypeName) { // Return a combined index: mainTypeIndex * 1000 + subTypeIndex return mainTypeIndex * 1000 + i; } } } } return -1; } string GetSubTypeNameFromIndex(int combinedIndex) { if (combinedIndex < 0 || shopLoader?.primaryShop?.mainTypes == null) return null; int mainTypeIndex = combinedIndex / 1000; int subTypeIndex = combinedIndex % 1000; if (mainTypeIndex >= 0 && mainTypeIndex < shopLoader.primaryShop.mainTypes.Count) { var mainType = shopLoader.primaryShop.mainTypes[mainTypeIndex]; if (subTypeIndex >= 0 && subTypeIndex < mainType.subTypes.Count) { return mainType.subTypes[subTypeIndex]; } } return null; } void CreateItemPanelFromPool(GShopItem item, int targetSiblingIndex) { GameObject itemPanel = null; if (useObjectPooling && itemPanelPool != null) { // Get panel from pool itemPanel = itemPanelPool.GetPanel(); } else { // Fallback to instantiate if pooling disabled if (itemPanelPrefab != null && itemContainer != null) { itemPanel = Instantiate(itemPanelPrefab, itemContainer); } } if (itemPanel != null && itemContainer != null) { // Set parent and position itemPanel.transform.SetParent(itemContainer, false); itemPanel.transform.localScale = Vector3.one; // Ensure deterministic ordering regardless of pool retrieval order itemPanel.transform.SetSiblingIndex(targetSiblingIndex); // Setup the panel ShopItemPanel itemPanelScript = itemPanel.GetComponent(); if (itemPanelScript != null) { itemPanelScript.SetupItem(item, this); } currentItemPanels.Add(itemPanel); } } void ReturnAllPanelsToPool() { if (useObjectPooling && itemPanelPool != null) { // Return all panels to pool foreach (GameObject panel in currentItemPanels) { if (panel != null) { itemPanelPool.ReturnPanel(panel); } } } else { // Destroy panels if not using pooling foreach (GameObject panel in currentItemPanels) { if (panel != null) Destroy(panel); } } currentItemPanels.Clear(); } public void ShowItemDetail(GShopItem item) { selectedItem = item; Debug.Log($"$ Local ID of selected item: {item.localId}"); if (shopDetailPanel != null) { shopDetailPanel.SetActive(true); // Get the ShopDetailPanel component and call its SetupDetailPanel method ShopDetailPanel detailPanelScript = shopDetailPanel.GetComponent(); if (detailPanelScript != null) { detailPanelScript.SetupDetailPanel(item, this); } else { Debug.LogError("ShopDetailPanel script not found on shopDetailPanel GameObject!"); // Fallback to the old method UpdateDetailPanel(item); detailScrollRect.verticalNormalizedPosition = 1f; LayoutRebuilder.ForceRebuildLayoutImmediate(detailScrollRect.content); } } } void UpdateDetailPanel(GShopItem item) { if (detailItemName != null) detailItemName.text = item.name; if (detailItemDescription != null) detailItemDescription.text = item.desc; if (detailItemQuantity != null) detailItemQuantity.text = $"Quantity: {item.num}"; // Find the best buy option (first non-zero price) uint bestPrice = 0; for (int i = 0; i < item.buy.Length; i++) { if (item.buy[i].price > 0) { bestPrice = item.buy[i].price; break; } } if (detailItemPrice != null) detailItemPrice.text = $"Price: {bestPrice}"; Debug.Log($"Detail item price: {detailItemPrice.text}, detail item price != null: {detailItemPrice != null}"); // Load icon based on item name (you'll need to implement icon loading) if (detailItemIcon != null) { LoadItemIcon(detailItemIcon, item.name); } } void LoadItemIcon(Image iconImage, string itemName) { // TODO: Implement icon loading based on item name // This is where you'd load the appropriate icon sprite // For now, we'll leave it empty for you to implement Debug.Log($"Loading icon for item: {itemName}"); } void CloseDetailPanel() { if (shopDetailPanel != null) shopDetailPanel.SetActive(false); } void OnBuyItem() { if (selectedItem.id == 0) return; // TODO: Implement purchase logic Debug.Log($"Attempting to buy item: {selectedItem.name} (ID: {selectedItem.id})"); // Close detail panel after purchase attempt CloseDetailPanel(); } void OnDestroy() { if (shopLoader != null && _shopLoaderEventsSubscribed) shopLoader.OnPrimaryShopLoaded -= OnPrimaryShopDataReady; // Clean up pooled objects if (useObjectPooling && itemPanelPool != null) { itemPanelPool.ReturnAllPanels(); } else { // Fallback cleanup foreach (GameObject panel in currentItemPanels) { if (panel != null) Destroy(panel); } } currentItemPanels.Clear(); } }