Files
test/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgProduce.cs
T
2026-04-13 17:08:35 +07:00

887 lines
28 KiB
C#

using BrewMonster.Network;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Task;
using BrewMonster.UI;
using ModelRenderer.Scripts.Common;
using PerfectWorld.Scripts.Managers;
using PerfectWorld.Scripts.Task;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using CSNetwork;
using System.Text;
using TMPro;
namespace BrewMonster
{
public class DlgProduce : AUIDialog
{
[Header("Produce Tabs")]
[SerializeField] private Transform tabBtnContainer;
[SerializeField] private GameObject tabBtnPb;
[SerializeField] private string tabButtonTextComponentName = "Text";
[Header("Produce Detail")]
[SerializeField] private ScrollRect m_scrollRect;
[SerializeField] private Transform itemContainer;
[SerializeField] private GameObject itemPb;
[Header("Quantity")]
[SerializeField] private TextMeshProUGUI quantityText;
[SerializeField] private Button quantityIncreaseBtn;
[SerializeField] private Button quantityDecreaseBtn;
[SerializeField] private Button quantityMaxBtn;
private int currentQuantity = 1;
private int produceRemainCount = 0;
private bool isProducing = false;
[Header("Material Slots")]
[SerializeField] private List<Transform> materialSlots = new List<Transform>();
[Header("Result Slot")]
[SerializeField] private Image itemResult;
[Header("Item Info Panel")]
public Transform itemInfoRoot;
public TextMeshProUGUI infoDescText;
//[Header("Wepon Info Equippment Panel")]
//[SerializeField] private Transform weponInfoRoot;
//[SerializeField] private TextMeshProUGUI weponNameInfoText;
//[SerializeField] private TextMeshProUGUI weponDescInfoText;
//[SerializeField] private TextMeshProUGUI weponExtraInfoText;
[Header("Default")]
[SerializeField] private Sprite khung_item;
private NPC_MAKE_SERVICE? cachedMakeService = null;
private int currentTabIndex = 0;
private uint selectedRecipeId = 0; // Track the currently selected recipe
static readonly Color COLOR_NOT_ENOUGH = new Color32(145, 145, 145, 255);
static readonly Color COLOR_ENOUGH = Color.white;
[SerializeField] private Button startProduceBtn;
[SerializeField] private Button cancelProduceBtn;
[SerializeField] private Button clickCatcherPanel;
enum ItemInfoMode
{
None,
ResultItem,
MaterialItem
}
private ItemInfoMode currentInfoMode = ItemInfoMode.None;
public override void Start()
{
quantityText.text = currentQuantity.ToString();
quantityDecreaseBtn.onClick.AddListener(OnClickDecreaseBtn);
quantityIncreaseBtn.onClick.AddListener(OnClickIncreaseBtn);
quantityMaxBtn.onClick.AddListener(OnClickMaxBtn);
clickCatcherPanel.onClick.AddListener(HideItemInfo);
}
public override void Update()
{
#if UNITY_EDITOR || UNITY_STANDALONE
if (Input.GetMouseButtonDown(0))
{
CheckHidePanel(Input.mousePosition);
}
#else
if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began)
{
CheckHidePanel(Input.GetTouch(0).position);
}
#endif
}
void CheckHidePanel(Vector2 screenPos)
{
if (!RectTransformUtility.RectangleContainsScreenPoint(
itemInfoRoot as RectTransform,
screenPos,
Camera.main))
{
HideItemInfo();
}
}
public void OpenProduce(uint npcId)
{
if (!LoadMakeService(npcId))
{
Debug.LogError("[DlgProduce] LoadMakeService failed");
return;
}
selectedRecipeId = 0; // Reset selected recipe
SetupButtonHandlers();
CreateTabs();
OnTabSelected(0);
}
void SetupButtonHandlers()
{
if (startProduceBtn != null)
{
startProduceBtn.onClick.RemoveAllListeners();
startProduceBtn.onClick.AddListener(OnStartProduceClicked);
}
if (cancelProduceBtn != null)
{
cancelProduceBtn.onClick.RemoveAllListeners();
cancelProduceBtn.onClick.AddListener(OnCancelProduceClicked);
}
}
bool LoadMakeService(uint npcId)
{
var edm = ElementDataManProvider.GetElementDataMan();
if (edm == null)
return false;
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
object npcData = edm.get_data_ptr(npcId, ID_SPACE.ID_SPACE_ESSENCE, ref dt);
if (dt != DATA_TYPE.DT_NPC_ESSENCE || npcData == null)
return false;
NPC_ESSENCE npc = (NPC_ESSENCE)npcData;
if (npc.id_make_service == 0)
return false;
DATA_TYPE serviceDt = DATA_TYPE.DT_INVALID;
object serviceData = edm.get_data_ptr(
npc.id_make_service,
ID_SPACE.ID_SPACE_ESSENCE,
ref serviceDt
);
if (serviceDt != DATA_TYPE.DT_NPC_MAKE_SERVICE || serviceData == null)
return false;
cachedMakeService = (NPC_MAKE_SERVICE)serviceData;
return true;
}
void CreateTabs()
{
ClearContainer(tabBtnContainer);
if (!cachedMakeService.HasValue || cachedMakeService.Value.pages == null)
return;
var makeService = cachedMakeService.Value;
for (int pageIndex = 0; pageIndex < makeService.pages.Length; pageIndex++)
{
var page = makeService.pages[pageIndex];
bool hasRecipes = false;
if (page.id_goods != null)
{
foreach (uint recipeId in page.id_goods)
{
if (recipeId != 0)
{
hasRecipes = true;
break;
}
}
}
if (!hasRecipes)
continue;
GameObject tabObj = Instantiate(tabBtnPb, tabBtnContainer);
tabObj.name = $"ProduceTab_Page_{pageIndex}";
tabObj.SetActive(true);
string pageTitle = ByteToStringUtils.UshortArrayToUnicodeString(page.page_title);
if (string.IsNullOrWhiteSpace(pageTitle))
pageTitle = $"Page {pageIndex + 1}";
TMPro.TextMeshProUGUI tabTMP = null;
TextMeshProUGUI tabText = null;
Transform textTf = tabObj.transform.Find(tabButtonTextComponentName);
if (textTf != null)
{
tabTMP = textTf.GetComponent<TMPro.TextMeshProUGUI>();
if (tabTMP == null)
{
tabText = textTf.GetComponent<TextMeshProUGUI>();
}
}
if (tabTMP == null && tabText == null)
{
tabTMP = tabObj.GetComponentInChildren<TMPro.TextMeshProUGUI>();
if(tabTMP == null)
{
tabText = tabObj.GetComponentInChildren<TextMeshProUGUI>();
}
}
if(tabTMP != null)
tabTMP.text = pageTitle;
else if(tabText != null)
tabText.text = pageTitle;
Button btn = tabObj.GetComponent<Button>();
if(btn == null)
btn = tabObj.GetComponentInChildren<Button>();
if(btn != null)
{
int capturedIndex = pageIndex;
btn.onClick.AddListener(() =>
{
OnTabSelected(capturedIndex);
ResetOnTop();
});
}
}
}
void OnTabSelected(int index)
{
currentTabIndex = index;
RefreshSubItemList();
}
void RefreshSubItemList()
{
ClearContainer(itemContainer);
if (!cachedMakeService.HasValue)
return;
var service = cachedMakeService.Value;
if (service.pages == null || currentTabIndex >= service.pages.Length)
return;
var page = service.pages[currentTabIndex];
if (page.id_goods == null)
return;
foreach (uint recipeId in page.id_goods)
{
if (recipeId == 0)
continue;
CreateSubItem(recipeId);
}
}
void CreateSubItem(uint recipeId)
{
if (itemPb == null || itemContainer == null)
return;
GameObject item = Instantiate(itemPb, itemContainer);
item.name = $"Recipe_{recipeId}";
item.SetActive(true);
Debug.Log("[DlgProduce] Creating produce item for recipe ID: " + item.name);
ProduceItemPanel panel = item.GetComponent<ProduceItemPanel>();
if(panel != null)
{
panel.Setup(recipeId,this);
}
else
{
Debug.LogWarning("[DlgProduce] ProduceItemPanel component not found on item prefab");
}
Button btn = item.GetComponent<Button>();
if (btn == null)
btn = item.AddComponent<Button>();
btn.onClick.RemoveAllListeners();
btn.onClick.AddListener(() =>
{
bool isNewRecipe = selectedRecipeId != recipeId;
selectedRecipeId = recipeId;
if (isNewRecipe)
{
currentQuantity = 1;
UpdateQuantityText(currentQuantity);
}
ShowRecipeMaterials(recipeId);
ShowItemInfoByRecipe(recipeId);
});
}
void ClearContainer(Transform container)
{
if (container == null)
return;
for (int i = container.childCount - 1; i >= 0; i--)
{
Destroy(container.GetChild(i).gameObject);
}
}
void ClearMaterialSlots()
{
foreach (var slot in materialSlots)
{
if (slot == null) continue;
Transform iconTf = slot.Find("item");
if (iconTf != null)
{
Image img = iconTf.GetComponent<Image>();
if (img != null)
img.sprite = khung_item;
}
Transform qtyTf = slot.Find("text_quantity");
if (qtyTf != null)
{
TextMeshProUGUI txt = qtyTf.GetComponent<TextMeshProUGUI>();
if (txt != null)
txt.text = "";
}
}
if (itemResult != null)
{
itemResult.sprite = khung_item;
}
}
public void ShowRecipeMaterials(uint recipeId)
{
selectedRecipeId = recipeId;
ClearMaterialSlots();
var edm = ElementDataManProvider.GetElementDataMan();
if (edm == null)
return;
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
object data = edm.get_data_ptr(recipeId, ID_SPACE.ID_SPACE_RECIPE, ref dt);
if (data is not RECIPE_ESSENCE recipe)
return;
if (itemResult != null &&
recipe.targets != null &&
recipe.targets.Length > 0 &&
recipe.targets[0].id_to_make != 0)
{
uint outputItemId = recipe.targets[0].id_to_make;
if (itemResult != null)
{
if (itemResult != null)
{
Sprite sp = EC_IvtrItemUtils.Instance.ResolveItemIconSprite((int)outputItemId);
if (sp != null)
{
itemResult.sprite = sp;
itemResult.enabled = true;
itemResult.preserveAspect = true;
}
}
}
Button resultBtn = itemResult.GetComponent<Button>();
if (resultBtn == null)
{
resultBtn = itemResult.gameObject.AddComponent<Button>();
}
resultBtn.onClick.RemoveAllListeners();
uint capturedOutputId = outputItemId;
resultBtn.onClick.AddListener(() =>
{
EC_IvtrItem outputItem = EC_IvtrItem.CreateItem((int)capturedOutputId, 0, 1);
outputItem?.GetDetailDataFromLocal();
ShowResultItemInfo(outputItem, capturedOutputId);
});
}
if (recipe.materials == null)
return;
bool allEnough = true;
int slotIndex = 0;
foreach (var mat in recipe.materials)
{
if (slotIndex >= materialSlots.Count)
break;
if (mat.id == 0 || mat.num <= 0)
continue;
// compute required amount for the currently selected quantity
int requiredAmount = mat.num * Mathf.Max(1, currentQuantity);
int owned = GetInventoryItemCount(mat.id);
bool enough = owned >= requiredAmount;
if (!enough)
allEnough = false;
Transform slot = materialSlots[slotIndex];
slot.gameObject.SetActive(true);
Button btn = slot.GetComponent<Button>();
if (btn == null)
btn = slot.gameObject.AddComponent<Button>();
uint capturedMatId = mat.id;
btn.onClick.RemoveAllListeners();
btn.onClick.AddListener(() =>
{
EC_IvtrItem matItem = EC_IvtrItem.CreateItem((int)capturedMatId, 0, 1);
matItem?.GetDetailDataFromLocal();
ShowMaterialItemInfo(matItem, capturedMatId);
});
Transform iconTf = slot.Find("item");
if (iconTf != null)
{
Image img = iconTf.GetComponent<Image>();
if (img != null)
{
Sprite sp = EC_IvtrItemUtils.Instance.ResolveItemIconSprite((int)mat.id);
img.sprite = sp;
img.enabled = true;
img.preserveAspect = true;
img.color = enough ? COLOR_ENOUGH : COLOR_NOT_ENOUGH;
}
}
Transform qtyTf = slot.Find("text_quantity");
if (qtyTf != null)
{
var tmp = qtyTf.GetComponent<TMPro.TextMeshProUGUI>();
if (tmp != null)
tmp.text = $"{requiredAmount}/{owned}";
else
{
TextMeshProUGUI txt = qtyTf.GetComponent<TextMeshProUGUI>();
if (txt != null)
txt.text = $"{requiredAmount}/{owned}";
}
}
slotIndex++;
}
if (itemResult != null)
{
itemResult.color = allEnough ? COLOR_ENOUGH : COLOR_NOT_ENOUGH;
}
if (startProduceBtn != null)
{
startProduceBtn.interactable = allEnough;
}
}
void OnStartProduceClicked()
{
if (selectedRecipeId == 0)
{
Debug.LogWarning("[DlgProduce] No recipe selected");
return;
}
if (!cachedMakeService.HasValue)
{
Debug.LogError("[DlgProduce] No make service cached");
return;
}
produceRemainCount = currentQuantity;
isProducing = true;
SendProduceOnce();
}
void SendProduceOnce()
{
if(!isProducing || produceRemainCount <= 0)
{
Debug.Log("[DlgProduce] No production needed or already completed");
return;
}
// Get skill ID from the service (not from recipe)
// The second parameter is the recipe ID (not the item ID)
int idSkill = (int)cachedMakeService.Value.id_make_skill;
int idRecipe = (int)selectedRecipeId;
uint dwCount = 1; // Default count is 1 (matching C++ code for Win_Produce1)
// Send the command to the server
// Parameters: idSkill (from service), idRecipe (recipe ID), dwCount
UnityGameSession.c2s_CmdNPCSevMakeItem(idSkill, idRecipe, dwCount);
Debug.Log($"[DlgProduce] Sent make item command: skill={idSkill}, recipe={idRecipe}, count={dwCount}");
}
void OnCancelProduceClicked()
{
CloseProduce();
ResetItemInfoPanel();
}
public void CloseProduce()
{
EC_Game.GetGameRun().GetUIManager().GetInGameUIMan().EndNPCService();
isProducing = false;
produceRemainCount = 0;
CloseDialogue();
ClearContainer(tabBtnContainer);
ClearContainer(itemContainer);
ClearMaterialSlots();
cachedMakeService = null;
currentTabIndex = 0;
selectedRecipeId = 0;
ResetItemInfoPanel();
HideItemInfo();
currentQuantity = 1;
}
// Called when production starts (NOTIFY_PRODUCE_START)
public void OnProduceStart(CSNetwork.GPDataType.cmd_produce_start cmd)
{
Debug.Log($"[DlgProduce] OnProduceStart: type={cmd.type}, use_time={cmd.use_time}, count={cmd.count}");
// TODO: Update progress bar, disable start button, etc.
// This would typically start a progress bar showing production time
if (startProduceBtn != null)
{
startProduceBtn.interactable = false;
}
}
// Called when one item is produced (NOTIFY_PRODUCE_END_ONE)
public void OnProduceOnce(CSNetwork.GPDataType.cmd_produce_once cmd)
{
Debug.Log($"[DlgProduce] OnProduceOnce: type={cmd.type}, amount={cmd.amount}, where={cmd.where}, index={cmd.index}");
// TODO: Update UI counters, progress, skill ability, etc.
// This would typically update the remaining count and progress
produceRemainCount--;
if (produceRemainCount > 0)
{
SendProduceOnce();
}
else
{
isProducing = false;
Debug.Log("[DlgProduce] Production completed for all items");
}
}
// Called when production ends (NOTIFY_PRODUCE_END)
public void OnProduceEnd()
{
Debug.Log("[DlgProduce] OnProduceEnd: Production completed");
// TODO: Re-enable start button, reset progress, etc.
if (startProduceBtn != null)
{
startProduceBtn.interactable = true;
}
}
// Called when production fails (NOTIFY_PRODUCE_NULL)
public void OnProduceNull(CSNetwork.GPDataType.cmd_produce_null cmd)
{
Debug.Log($"[DlgProduce] OnProduceNull: type={cmd.type} - Production failed");
// TODO: Show error message, re-enable start button, etc.
if (startProduceBtn != null)
{
startProduceBtn.interactable = true;
}
isProducing = false;
produceRemainCount = 0;
}
public void ShowItemInfoByRecipe(uint recipeId)
{
if (itemInfoRoot == null)
return;
var edm = ElementDataManProvider.GetElementDataMan();
if (edm == null)
return;
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
object data = edm.get_data_ptr(recipeId, ID_SPACE.ID_SPACE_RECIPE, ref dt);
if (data is not RECIPE_ESSENCE recipe ||
recipe.targets == null ||
recipe.targets.Length == 0)
return;
uint itemId = recipe.targets[0].id_to_make;
if (itemId == 0)
return;
EC_IvtrItem item = null;
try
{
item = EC_IvtrItem.CreateItem((int)itemId, 0, 1);
item?.GetDetailDataFromLocal();
}
catch { }
if (item == null)
return;
itemInfoRoot.gameObject.SetActive(true);
if (infoDescText != null)
infoDescText.text = EC_Utility.ProcessColorCodes(new StringBuilder (item.GetDesc()?.Replace("\\r", "\n") ?? ""));
ShowResultItemInfo(item, itemId);
}
public void HideItemInfo()
{
if (itemInfoRoot != null)
itemInfoRoot.gameObject.SetActive(false);
//if(weponInfoRoot != null)
// weponInfoRoot.gameObject.SetActive(false);
}
private void UpdateQuantityText(int quantity)
{
if (quantityText != null)
{
quantityText.text = quantity.ToString();
}
}
private void OnClickIncreaseBtn()
{
currentQuantity++;
if (selectedRecipeId != 0)
{
int max = CalculateMaxProduceCount(selectedRecipeId);
if (max > 0 && currentQuantity > max)
currentQuantity = max;
}
UpdateQuantityText(currentQuantity);
if (selectedRecipeId != 0)
ShowRecipeMaterials(selectedRecipeId);
}
private void OnClickDecreaseBtn()
{
if (currentQuantity > 1)
{
currentQuantity--;
UpdateQuantityText(currentQuantity);
if(selectedRecipeId != 0)
ShowRecipeMaterials(selectedRecipeId);
}
}
// Helper to get total count of an item in player's inventory
int GetInventoryItemCount(uint itemId)
{
var host = CECGameRun.Instance?.GetHostPlayer();
if (host == null)
return 0;
var inv = host.GetInventory(0); // 0 = inventory bag
if (inv == null)
return 0;
int total = 0;
int size = inv.GetSize();
for (int i = 0; i < size; i++)
{
var item = inv.GetItem(i, false);
if (item != null && item.m_tid == itemId)
{
total += item.m_iCount;
}
}
return total;
}
int CalculateMaxProduceCount(uint recipeId)
{
var edm = ElementDataManProvider.GetElementDataMan();
if (edm == null)
return 0;
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
object data = edm.get_data_ptr(recipeId, ID_SPACE.ID_SPACE_RECIPE, ref dt);
if (data is not RECIPE_ESSENCE recipe || recipe.materials == null)
return 0;
int maxCount = int.MaxValue;
foreach (var mat in recipe.materials)
{
if (mat.id == 0 || mat.num <= 0)
continue;
int owned = GetInventoryItemCount(mat.id);
int canMake = owned / mat.num;
if (canMake < maxCount)
maxCount = canMake;
}
if (maxCount == int.MaxValue)
return 0;
return Mathf.Max(0, maxCount);
}
void OnClickMaxBtn()
{
if (selectedRecipeId == 0)
return;
int max = CalculateMaxProduceCount(selectedRecipeId);
if (max <= 0)
{
currentQuantity = 1;
}
else
{
currentQuantity = max;
}
UpdateQuantityText(currentQuantity);
ShowRecipeMaterials(selectedRecipeId);
Debug.Log($"[DlgProduce] Max produce quantity = {currentQuantity}");
}
void ResetItemInfoPanel()
{
currentInfoMode = ItemInfoMode.None;
if (infoDescText != null)
infoDescText.text = string.Empty;
HideItemInfo();
}
void ForceRefreshItemInfoLayout()
{
infoDescText?.ForceMeshUpdate();
var rt = itemInfoRoot.GetComponent<RectTransform>();
if (rt != null)
LayoutRebuilder.ForceRebuildLayoutImmediate(rt);
}
void ShowResultItemInfo(EC_IvtrItem item, uint itemId)
{
ResetItemInfoPanel();
currentInfoMode = ItemInfoMode.ResultItem;
itemInfoRoot.gameObject.SetActive(true);
//infoNameText.text = EC_Utility.ProcessColorCodes(new StringBuilder(item.GetName()));
infoDescText.text = EC_Utility.ProcessColorCodes(
new StringBuilder(item.GetDesc()?.Replace("\\r", "\n") ?? "")
);
ForceRefreshItemInfoLayout();
//ShowEquippmentWeponInfo();
}
void ShowMaterialItemInfo(EC_IvtrItem item, uint itemId)
{
ResetItemInfoPanel();
currentInfoMode = ItemInfoMode.MaterialItem;
itemInfoRoot.gameObject.SetActive(true);
infoDescText.text = item.GetDesc();
ForceRefreshItemInfoLayout();
}
private EC_IvtrItem GetquippedWepon()
{
var host = CECGameRun.Instance?.GetHostPlayer();
if (host == null)
return null;
var currentEquipment = host.GetEquipment();
if (currentEquipment == null)
return null;
var srcItem = currentEquipment.GetItem(0);
if (srcItem != null && srcItem.m_iCID == (int)EC_IvtrItem.InventoryClassId.ICID_WEAPON)
{
var item = EC_IvtrItem.CreateItem(srcItem.m_tid, srcItem.m_expire_date, 1);
if (srcItem.Content != null && srcItem.Content.Length > 0)
item.SetItemInfo(srcItem.Content, srcItem.Content.Length);
return item;
}
return null;
}
//private void ShowEquippmentWeponInfo()
//{
// if (weponInfoRoot == null)
// return;
// var weponItem = GetquippedWepon();
// if (weponItem != null)
// {
// weponInfoRoot.gameObject.SetActive(true);
// //weponNameInfoText.text = weponItem.GetName();
// Debug.Log("[DlgProduce] Showing weapon info: " + weponItem);
// weponDescInfoText.text = "<color=#00FF00>[Được trang bị]</color> \n" + EC_Utility.ProcessColorCodes(new StringBuilder(weponItem.GetDesc().Replace("\\r", "\n")));
// //weponExtraInfoText.text = $"Item ID: {weponItem.m_tid}";
// var rt = weponInfoRoot.GetComponent<RectTransform>();
// if (rt != null)
// LayoutRebuilder.ForceRebuildLayoutImmediate(rt);
// }
// else
// {
// weponInfoRoot.gameObject.SetActive(false);
// }
//}
private void ResetOnTop()
{
m_scrollRect.verticalNormalizedPosition = 1f;
}
public void OnDestroy()
{
ClearContainer(tabBtnContainer);
ClearContainer(itemContainer);
}
}
}