From dd96ccd0ed5bd96fede3088cee1c3910a712a5e0 Mon Sep 17 00:00:00 2001 From: HungDK <> Date: Thu, 15 Jan 2026 15:05:22 +0700 Subject: [PATCH] Add request server to produce item --- .../CSNetwork/C2SCommand/C2SCommand.cs | 7 ++ .../CSNetwork/C2SCommand/C2SCommandFactory.cs | 19 +++ .../Scripts/Network/CSNetwork/GPDataType.cs | 24 ++++ .../Scripts/Network/CSNetwork/GameSession.cs | 15 ++- .../Scripts/Network/UnityGameSession.cs | 5 + .../Scripts/UI/Dialogs/DlgProduce.cs | 110 ++++++++++++++++-- Assets/PerfectWorld/UI/DlgProduce.prefab | 78 +------------ Assets/Scripts/CECHostPlayer.cs | 96 ++++++++++++++- 8 files changed, 269 insertions(+), 85 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs index e544775d8d..a295956f24 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs @@ -1447,6 +1447,13 @@ namespace CSNetwork.S2CCommand public int idTask; } + public struct NPCSevMakeItemCONTENT + { + public int idSkill; + public int idItem; + public uint dwCount; + } + public struct cmd_sevnpc_serve2 { public int service_type; diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs index 7c2b26dbbf..0efa3a9bdd 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs @@ -686,6 +686,25 @@ namespace CSNetwork.C2SCommand return octets; } + + public static Octets CreateNPCSevMakeItemCmd(int idSkill, int idItem, uint dwCount) + { + var cmd = new cmd_sevnpc_serve + { + service_type = NPC_service_type.GP_NPCSEV_MAKEITEM, + len = (uint)Marshal.SizeOf() + }; + + NPCSevMakeItemCONTENT content = new NPCSevMakeItemCONTENT() + { + idSkill = idSkill, + idItem = idItem, + dwCount = dwCount + }; + + return SerializeCommand(CommandID.SEVNPC_SERVE, cmd, content); + } + public static Octets CreateEmoteActionCmd(int wPose) { cmd_emote_action pCmd = new cmd_emote_action() diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs index e9cc4bac9d..3059e57888 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs @@ -1369,6 +1369,30 @@ namespace CSNetwork.GPDataType public byte byPackage; public byte bySlot; } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct cmd_produce_start + { + public ushort use_time; + public ushort count; + public int type; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct cmd_produce_once + { + public int type; + public uint amount; + public uint slot_amount; + public byte where; // Which package: 0 standard, 2 trash, 1 equip + public byte index; // Which slot in that package + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct cmd_produce_null + { + public int type; + } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct info_matter { diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs index 3f0e0d4fbb..c053fc48a5 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs @@ -512,7 +512,7 @@ namespace CSNetwork break; case CommandID.PICKUP_ITEM: case CommandID.HOST_OBTAIN_ITEM: - // case CommandID.PRODUCE_ONCE: + case CommandID.PRODUCE_ONCE: case CommandID.TASK_DELIVER_ITEM: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PICKUPITEM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); @@ -807,6 +807,13 @@ namespace CSNetwork case CommandID.FLYSWORD_TIME: EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_FLYSWORDTIME, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); break; + case CommandID.PRODUCE_START: + case CommandID.PRODUCE_END: + case CommandID.PRODUCE_NULL: + // Post MSG_HST_PRODUCEITEM message with command ID as parameter (matches C++ behavior) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PRODUCEITEM, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + } } @@ -1254,6 +1261,12 @@ namespace CSNetwork gamedatasend.Data = C2SCommandFactory.CreateNPCSevWaypointCmd(NPC_service_type.GP_NPCSEV_WAYPOINT, 0); SendProtocol(gamedatasend); } + public void c2s_SendCmdNPCSevMakeItem(int idSkill, int idItem, uint dwCount) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevMakeItemCmd(idSkill, idItem, dwCount); + SendProtocol(gamedatasend); + } public void GetRoleBaseInfo(int iNumRole, List aRoleIDs) { int iNumLimit = 128; diff --git a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs index 7bfdb79240..169a54a821 100644 --- a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs @@ -292,6 +292,11 @@ namespace BrewMonster.Network Instance._gameSession.c2s_SendCmdNPCSevWaypoint(); } + public static void c2s_CmdNPCSevMakeItem(int idSkill, int idItem, uint dwCount) + { + Instance._gameSession.c2s_SendCmdNPCSevMakeItem(idSkill, idItem, dwCount); + } + public void GetFactionInfo(int iNumFaction, int[] aFactinoIDs) { m_stubbornFactionInfoSender.Add(iNumFaction, aFactinoIDs); diff --git a/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgProduce.cs b/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgProduce.cs index f00569d54a..0f43e1046e 100644 --- a/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgProduce.cs +++ b/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgProduce.cs @@ -9,28 +9,33 @@ using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; +using CSNetwork; namespace BrewMonster { public class DlgProduce : AUIDialog { [Header("Produce Tabs")] - public Transform tabBtnContainer; - public GameObject tabBtnPb; - public string tabButtonTextComponentName = "Text"; + [SerializeField] private Transform tabBtnContainer; + [SerializeField] private GameObject tabBtnPb; + [SerializeField] private string tabButtonTextComponentName = "Text"; [Header("Produce Detail")] - public Transform itemContainer; - public GameObject itemPb; + [SerializeField] private Transform itemContainer; + [SerializeField] private GameObject itemPb; [Header("Material Slots")] - public List materialSlots = new List(); + [SerializeField] private List materialSlots = new List(); [Header("Result Slot")] - public Transform itemResult; + [SerializeField] private Transform itemResult; private NPC_MAKE_SERVICE? cachedMakeService = null; private int currentTabIndex = 0; + private uint selectedRecipeId = 0; // Track the currently selected recipe + + [SerializeField] private Button startProduceBtn; + [SerializeField] private Button cancelProduceBtn; public void OpenProduce(uint npcId) { @@ -40,10 +45,27 @@ namespace BrewMonster 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(); @@ -261,6 +283,7 @@ namespace BrewMonster public void ShowRecipeMaterials(uint recipeId) { + selectedRecipeId = recipeId; // Track the selected recipe ClearMaterialSlots(); var edm = ElementDataManProvider.GetElementDataMan(); @@ -350,6 +373,37 @@ namespace BrewMonster } } + void OnStartProduceClicked() + { + if (selectedRecipeId == 0) + { + Debug.LogWarning("[DlgProduce] No recipe selected"); + return; + } + + if (!cachedMakeService.HasValue) + { + Debug.LogError("[DlgProduce] No make service cached"); + 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() { gameObject.SetActive(false); @@ -358,9 +412,51 @@ namespace BrewMonster 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 + } + + // 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; + } + } public void OnDestroy() { diff --git a/Assets/PerfectWorld/UI/DlgProduce.prefab b/Assets/PerfectWorld/UI/DlgProduce.prefab index f2790da292..a6851fc9f5 100644 --- a/Assets/PerfectWorld/UI/DlgProduce.prefab +++ b/Assets/PerfectWorld/UI/DlgProduce.prefab @@ -3959,81 +3959,6 @@ MonoBehaviour: m_OnClick: m_PersistentCalls: m_Calls: [] ---- !u!1 &2703059059040193158 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 7991120577813580350} - - component: {fileID: 1861257096977318129} - - component: {fileID: 4248307541097962793} - m_Layer: 0 - m_Name: Mask - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!224 &7991120577813580350 -RectTransform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2703059059040193158} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1.6, y: 1.6, z: 1.6} - m_ConstrainProportionsScale: 1 - m_Children: [] - m_Father: {fileID: 8062547726938481465} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 0, y: 0} - m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &1861257096977318129 -CanvasRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2703059059040193158} - m_CullTransparentMesh: 1 ---- !u!114 &4248307541097962793 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2703059059040193158} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: - m_Material: {fileID: 0} - m_Color: {r: 0, g: 0, b: 0, a: 0} - m_RaycastTarget: 1 - m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} - m_Maskable: 1 - m_OnCullStateChanged: - m_PersistentCalls: - m_Calls: [] - m_Sprite: {fileID: 0} - m_Type: 0 - m_PreserveAspect: 0 - m_FillCenter: 1 - m_FillMethod: 4 - m_FillAmount: 1 - m_FillClockwise: 1 - m_FillOrigin: 0 - m_UseSpriteMesh: 0 - m_PixelsPerUnitMultiplier: 1 --- !u!1 &2805715107032174497 GameObject: m_ObjectHideFlags: 0 @@ -7242,6 +7167,8 @@ MonoBehaviour: - {fileID: 7749576438893503211} - {fileID: 4622013364428910847} itemResult: {fileID: 8891250597797895463} + startProduceBtn: {fileID: 104721682242719380} + cancelProduceBtn: {fileID: 786694399164181467} --- !u!1 &5819068069398175026 GameObject: m_ObjectHideFlags: 0 @@ -8472,7 +8399,6 @@ RectTransform: m_ConstrainProportionsScale: 1 m_Children: - {fileID: 8572018612644380183} - - {fileID: 7991120577813580350} - {fileID: 6993523375080496617} m_Father: {fileID: 1108604418086848939} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 1454d1da05..019d3d397a 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -536,6 +536,9 @@ namespace BrewMonster case int value when value == EC_MsgDef.MSG_HST_PICKUPITEM: OnMsgHstPickupItem(Msg); break; + case int value when value == EC_MsgDef.MSG_HST_PRODUCEITEM: + OnMsgHstProduceItem(Msg); + break; case int value when value == EC_MsgDef.MSG_HST_SELTARGET: OnMsgHstSelTarget(Msg); break; case int value when value == EC_MsgDef.MSG_HST_ATKRESULT: OnMsgHstAttackResult(Msg); break; @@ -2014,9 +2017,100 @@ namespace BrewMonster } break; + case CommandID.PRODUCE_ONCE: + { + // Parse cmd_produce_once struct data + cmd_produce_once produceCmd = GPDataTypeHelper.FromBytes(data); + + int produceItemId = produceCmd.type; + int produceExpireDate = 0; + uint produceAmount = produceCmd.amount; + byte producePack = produceCmd.where; + byte produceSlot = produceCmd.index; + // Create new inventory item data + var produceNewItem = new EC_IvtrItem + { + Package = producePack, + Slot = produceSlot, + m_tid = produceItemId, + m_expire_date = produceExpireDate, + State = 0, + m_iCount = (int)produceAmount, + Crc = 0, + Content = null + }; - // TODO: Handle other pickup item commands if necessary + // Add item to inventory + var produce_ivt = GetInventory(producePack); + if (!produce_ivt.MergeItem(produceItemId, produceExpireDate, (int)produceAmount, out var produceLastSlot, out var produceSlotNum) || + produceLastSlot != produceSlot || produceSlotNum != (int)produceCmd.slot_amount) + { + Debug.LogWarning($"[PRODUCE_ONCE] Failed to merge item {produceItemId} to package {producePack}, slot {produceSlot}"); + return; + } + produce_ivt.SetItem(produceSlot, produceNewItem); + + Debug.Log($"[PRODUCE_ONCE] Successfully added produced item {produceItemId} to package {producePack}, slot {produceSlot} with count {produceAmount}"); + + // Trigger UI refresh if an EC_InventoryUI is present in scene + var produce_ui = GameObject.FindFirstObjectByType(); + if (produce_ui != null) + { + produce_ui.RefreshAll(); + } + + UpdateEquipSkins(); + + // Notify DlgProduce about successful produce (NOTIFY_PRODUCE_END_ONE) + var dlgProduce = GameObject.FindFirstObjectByType(); + if (dlgProduce != null) + { + dlgProduce.OnProduceOnce(produceCmd); + } + } + break; + } + } + + public void OnMsgHstProduceItem(in ECMSG Msg) + { + var data = Msg.dwParam1 as byte[]; + int cmd = Convert.ToInt32(Msg.dwParam2); + + // Get DlgProduce to notify + var dlgProduce = GameObject.FindFirstObjectByType(); + if (dlgProduce == null) + { + Debug.LogWarning("[OnMsgHstProduceItem] DlgProduce not found"); + return; + } + + switch (cmd) + { + case CommandID.PRODUCE_START: + { + cmd_produce_start pCmd = GPDataTypeHelper.FromBytes(data); + Debug.Log($"[PRODUCE_START] type={pCmd.type}, use_time={pCmd.use_time}, count={pCmd.count}"); + dlgProduce.OnProduceStart(pCmd); + } + break; + case CommandID.PRODUCE_END: + { + Debug.Log("[PRODUCE_END] Production ended"); + dlgProduce.OnProduceEnd(); + } + break; + case CommandID.PRODUCE_NULL: + { + cmd_produce_null pCmd = GPDataTypeHelper.FromBytes(data); + Debug.Log($"[PRODUCE_NULL] type={pCmd.type}"); + dlgProduce.OnProduceNull(pCmd); + } + break; + default: + Debug.LogWarning($"[OnMsgHstProduceItem] Unknown command: {cmd}"); + break; } }