Files
test/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgProduce.cs
T
2026-01-16 17:28:55 +07:00

743 lines
23 KiB
C#

using BrewMonster.Network;
using BrewMonster.Scripts.Managers;
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 Transform itemContainer;
[SerializeField] private GameObject itemPb;
[Header("Quantity")]
[SerializeField] private List<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 Transform itemResult;
[Header("Item Info Panel")]
public Transform itemInfoRoot;
public TextMeshProUGUI infoNameText;
public TextMeshProUGUI infoDescText;
public TextMeshProUGUI infoExtraText;
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;
public override void Start()
{
quantityText[0].text = currentQuantity.ToString();
quantityText[1].text = currentQuantity.ToString();
quantityDecreaseBtn.onClick.AddListener(OnClickDecreaseBtn);
quantityIncreaseBtn.onClick.AddListener(OnClickIncreaseBtn);
if(quantityMaxBtn != null)
{
quantityMaxBtn.onClick.AddListener(OnClickMaxBtn);
}
}
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));
}
}
}
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");
}
var trigger = item.AddComponent<UnityEngine.EventSystems.EventTrigger>();
var entryEnter = new UnityEngine.EventSystems.EventTrigger.Entry
{
eventID = UnityEngine.EventSystems.EventTriggerType.PointerEnter
};
entryEnter.callback.AddListener((_) =>
{
ShowItemInfoByRecipe(recipeId);
});
trigger.triggers.Add(entryEnter);
var entryExit = new UnityEngine.EventSystems.EventTrigger.Entry
{
eventID = UnityEngine.EventSystems.EventTriggerType.PointerExit
};
entryExit.callback.AddListener((_) =>
{
HideItemInfo();
});
trigger.triggers.Add(entryExit);
}
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;
slot.gameObject.SetActive(false);
Transform iconTf = slot.Find("item");
if (iconTf != null)
{
Image img = iconTf.GetComponent<Image>();
if (img != null)
img.sprite = null;
}
Transform qtyTf = slot.Find("text_quantity");
if (qtyTf != null)
{
TextMeshProUGUI txt = qtyTf.GetComponent<TextMeshProUGUI>();
if (txt != null)
txt.text = "";
}
}
if (itemResult != null)
{
itemResult.gameObject.SetActive(false);
Transform iconTf = itemResult.Find("item");
if (iconTf != null)
{
Image img = iconTf.GetComponent<Image>();
if (img != null)
img.sprite = null;
}
Transform qtyTf = itemResult.Find("text_quantity");
if (qtyTf != null)
{
TextMeshProUGUI txt = qtyTf.GetComponent<TextMeshProUGUI>();
if (txt != null)
txt.text = "";
}
}
}
public void ShowRecipeMaterials(uint recipeId)
{
selectedRecipeId = recipeId; // Track the selected recipe
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;
itemResult.gameObject.SetActive(true);
Transform iconTf = itemResult.Find("item");
if (iconTf != null)
{
Image img = iconTf.GetComponent<Image>();
if (img != null)
{
Sprite sp = EC_IvtrItemUtils.Instance.ResolveItemIconSprite((int)outputItemId);
if (sp != null)
{
img.sprite = sp;
img.enabled = true;
img.preserveAspect = true;
}
}
}
Transform qtyTf = itemResult.Find("text_quantity");
if (qtyTf != null)
{
TextMeshProUGUI txt = qtyTf.GetComponent<TextMeshProUGUI>();
if (txt != null)
txt.text = "1";
}
}
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;
int owned = GetInventoryItemCount(mat.id);
bool enough = owned >= mat.num;
if (!enough)
allEnough = false;
Transform slot = materialSlots[slotIndex];
slot.gameObject.SetActive(true);
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 = $"{mat.num}";
else
{
TextMeshProUGUI txt = qtyTf.GetComponent<TextMeshProUGUI>();
if (txt != null)
txt.text = $"{mat.num}";
}
}
slotIndex++;
}
if (itemResult != null)
{
Transform iconTf = itemResult.Find("item");
if (iconTf != null)
{
Image img = iconTf.GetComponent<Image>();
if (img != null)
{
img.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();
}
public void CloseProduce()
{
isProducing = false;
produceRemainCount = 0;
gameObject.SetActive(false);
ClearContainer(tabBtnContainer);
ClearContainer(itemContainer);
ClearMaterialSlots();
cachedMakeService = null;
currentTabIndex = 0;
selectedRecipeId = 0;
Debug.Log("[DlgProduce] Produce dialog closed");
}
// 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 (infoNameText != null)
infoNameText.text = EC_Utility.ProcessColorCodes(new StringBuilder (item.GetName()));
if (infoDescText != null)
infoDescText.text = EC_Utility.ProcessColorCodes(new StringBuilder (item.GetDesc()?.Replace("\\r", "\n") ?? ""));
if (infoExtraText != null)
{
infoExtraText.text = $"Item ID: {itemId}";
}
}
public void HideItemInfo()
{
if (itemInfoRoot != null)
itemInfoRoot.gameObject.SetActive(false);
}
private void UpdateQuantityText(int quantity)
{
if (quantityText != null)
{
quantityText[0].text = quantity.ToString();
quantityText[1].text = quantity.ToString();
}
}
private void OnClickIncreaseBtn()
{
currentQuantity++;
UpdateQuantityText(currentQuantity);
}
private void OnClickDecreaseBtn()
{
if (currentQuantity > 1)
{
currentQuantity--;
UpdateQuantityText(currentQuantity);
}
}
// 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}");
}
public void OnDestroy()
{
ClearContainer(tabBtnContainer);
ClearContainer(itemContainer);
}
}
}