Merge pull request 'feature/storage' (#469) from feature/storage into develop

Reviewed-on: https://git.pthub.vn/Unity/perfect-world-unity/pulls/469
This commit is contained in:
hungdk
2026-05-27 05:16:52 +00:00
16 changed files with 4724 additions and 13 deletions
@@ -69,3 +69,7 @@ MonoBehaviour:
prefab: {fileID: 8147986291757959694, guid: 11d09ee52b0c5f24fb3ef21e177ebe2d, type: 3}
- id: EC_AccountStorageUI
prefab: {fileID: 3837460183159982207, guid: 52c6d600a10af6b46a93fdc0ab719198, type: 3}
- id: DlgStoragePW
prefab: {fileID: 3837460183159982207, guid: 54acd8492cecae04aaf22d12852c19b4, type: 3}
- id: DlgStorageChangePW
prefab: {fileID: 3837460183159982207, guid: ebb0c4a44747f664f9e9071c09b83370, type: 3}
@@ -1732,6 +1732,14 @@ namespace CSNetwork.S2CCommand
{
public uint psw_size;
}
// PW_CPP: EC_SendC2SCmds.cpp c2s_SendCmdNPCSevChgTrashPsw CONTENT
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ChgTrashPswCONTENT
{
public ushort origin_size;
public ushort new_size;
}
}
// Player and NPC state
@@ -1268,6 +1268,27 @@ namespace CSNetwork.C2SCommand
return octets;
}
public static Octets CreateNPCSevChgTrashPswCmd(string oldPassword, string newPassword)
{
byte[] oldBytes = string.IsNullOrEmpty(oldPassword) ? null : Encoding.UTF8.GetBytes(oldPassword);
byte[] newBytes = string.IsNullOrEmpty(newPassword) ? null : Encoding.UTF8.GetBytes(newPassword);
ushort oldLen = (ushort)(oldBytes?.Length ?? 0);
ushort newLen = (ushort)(newBytes?.Length ?? 0);
var cmd = new cmd_sevnpc_serve
{
service_type = NPC_service_type.GP_NPCSEV_TRASHPSW,
len = (uint)Marshal.SizeOf<ChgTrashPswCONTENT>() + oldLen + newLen
};
var content = new ChgTrashPswCONTENT { origin_size = oldLen, new_size = newLen };
var octets = SerializeCommand(CommandID.SEVNPC_SERVE, cmd, content);
if (oldLen > 0 && oldBytes != null)
octets.Insert(octets.Size, oldBytes);
if (newLen > 0 && newBytes != null)
octets.Insert(octets.Size, newBytes);
return octets;
}
public static Octets CreateNPCSevOpenAccountBoxCmd()
{
var cmd = new cmd_sevnpc_serve
@@ -3129,6 +3129,15 @@ namespace CSNetwork
SendProtocol(req);
}
public void c2s_CmdNPCSevChgTrashPsw(string oldPassword, string newPassword)
{
var req = new gamedatasend
{
Data = C2SCommandFactory.CreateNPCSevChgTrashPswCmd(oldPassword ?? "", newPassword ?? "")
};
SendProtocol(req);
}
public void c2s_CmdNPCSevOpenAccountBox()
{
var req = new gamedatasend { Data = C2SCommandFactory.CreateNPCSevOpenAccountBoxCmd() };
@@ -983,6 +983,11 @@ namespace BrewMonster.Network
Instance._gameSession.c2s_CmdNPCSevOpenTrash(password);
}
public static void c2s_CmdNPCSevChgTrashPsw(string oldPassword, string newPassword)
{
Instance._gameSession.c2s_CmdNPCSevChgTrashPsw(oldPassword, newPassword);
}
public static void c2s_CmdNPCSevOpenAccountBox()
{
Instance._gameSession.c2s_CmdNPCSevOpenAccountBox();
@@ -3767,8 +3767,7 @@ namespace BrewMonster.UI
{
if (GetHostPlayer().TrashBoxHasPsw())
{
dialogue1 = "Win_InputString";
// PW_TODO: password input → c2s_CmdNPCSevOpenTrash(password)
dialogue1 = DlgStoragePW.DialogId;
}
else
{
@@ -3778,8 +3777,7 @@ namespace BrewMonster.UI
}
else if (idFunction == (int)SERVICE_TYPE.NPC_STORAGE_PASSWORD)
{
dialogue1 = "Win_InputString3";
// pShow1 = m_pAUIManager.GetDialog("Win_InputString3");
dialogue1 = DlgStorageChangePW.DialogId;
}
else if (idFunction == (int)SERVICE_TYPE.NPC_ACCOUNT_STORAGE)
{
@@ -0,0 +1,243 @@
using BrewMonster.Network;
using BrewMonster.Scripts;
using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace BrewMonster.UI
{
/// <summary>
/// Change warehouse password (C++ CDlgStorageChangePW / Win_InputString3).
/// Dialog id for <see cref="CECUIManager.ShowUI"/>: <see cref="DialogId"/>.
/// </summary>
public class DlgStorageChangePW : AUIDialog
{
public const string DialogId = "DlgStorageChangePW";
const int StringIdPasswordMismatch = 254;
[SerializeField] private TMP_InputField oldPasswordInput;
[SerializeField] private TMP_InputField newPasswordInput;
[SerializeField] private TMP_InputField confirmPasswordInput;
[SerializeField] private Button confirmButton;
[SerializeField] private Button cancelButton;
[SerializeField] private Button closeButton;
public static DlgStorageChangePW GetDialog()
{
return EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan()?.GetDialog(DialogId) as DlgStorageChangePW;
}
public override void Awake()
{
base.Awake();
ResolveControls();
WireButtons();
}
public override void OnShowDialogue()
{
base.OnShowDialogue();
WireButtons();
ClearFields();
}
void ResolveControls()
{
oldPasswordInput = DlgStoragePasswordUiHelper.FindInputField(transform, oldPasswordInput, "DEFAULT_Txt_Input");
newPasswordInput = DlgStoragePasswordUiHelper.FindInputField(transform, newPasswordInput, "Txt_New");
confirmPasswordInput = DlgStoragePasswordUiHelper.FindInputField(transform, confirmPasswordInput, "Txt_NewConfirm");
EnsurePasswordFieldLayout();
DlgStoragePasswordUiHelper.ConfigurePasswordField(oldPasswordInput);
DlgStoragePasswordUiHelper.ConfigurePasswordField(newPasswordInput);
DlgStoragePasswordUiHelper.ConfigurePasswordField(confirmPasswordInput);
DlgStoragePasswordUiHelper.ResolveButton(ref confirmButton, transform, "confirm", "btn_decide", "btn_confirm", "ok");
DlgStoragePasswordUiHelper.ResolveButton(ref cancelButton, transform, "cancel", "idcanel", "btn_cancel", "btn_exit");
DlgStoragePasswordUiHelper.ResolveButton(ref closeButton, transform, "btn_close", "close");
}
void WireButtons()
{
DlgStoragePasswordUiHelper.ResolveButton(ref confirmButton, transform, "confirm", "btn_decide", "btn_confirm", "ok");
DlgStoragePasswordUiHelper.WireConfirmButton(transform, OnConfirm, confirmButton);
DlgStoragePasswordUiHelper.WireCancelButtons(transform, OnCancel);
}
void EnsurePasswordFieldLayout()
{
if (oldPasswordInput == null)
return;
if (newPasswordInput == null)
newPasswordInput = DlgStoragePasswordUiHelper.ClonePasswordField(oldPasswordInput, "Txt_New", new Vector2(0f, -22f));
if (confirmPasswordInput == null)
confirmPasswordInput = DlgStoragePasswordUiHelper.ClonePasswordField(oldPasswordInput, "Txt_NewConfirm", new Vector2(0f, -44f));
}
void ClearFields()
{
if (oldPasswordInput != null)
oldPasswordInput.SetTextWithoutNotify(string.Empty);
if (newPasswordInput != null)
newPasswordInput.SetTextWithoutNotify(string.Empty);
if (confirmPasswordInput != null)
confirmPasswordInput.SetTextWithoutNotify(string.Empty);
}
void OnConfirm()
{
string oldPw = oldPasswordInput != null ? oldPasswordInput.text : string.Empty;
string newPw = newPasswordInput != null ? newPasswordInput.text : string.Empty;
string confirmPw = confirmPasswordInput != null ? confirmPasswordInput.text : string.Empty;
if (newPw != confirmPw)
{
var gameUi = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan();
gameUi?.ShowErrorMsg(GetStringFromTable(StringIdPasswordMismatch), string.Empty);
return;
}
UnityGameSession.c2s_CmdNPCSevChgTrashPsw(oldPw, newPw);
CloseDialog();
}
void OnCancel()
{
CECUIManager.Instance?.EndNpcTalkAfterStorageService();
}
void CloseDialog()
{
CECUIManager.Instance?.EndNpcTalkAfterStorageService();
}
}
static class DlgStoragePasswordUiHelper
{
public static TMP_InputField FindInputField(Transform root, TMP_InputField assigned, string controlName)
{
if (assigned != null)
return assigned;
var direct = root.Find(controlName);
if (direct != null && direct.TryGetComponent(out TMP_InputField directField))
return directField;
var all = root.GetComponentsInChildren<TMP_InputField>(true);
for (int i = 0; i < all.Length; i++)
{
if (all[i] != null && string.Equals(all[i].name, controlName, StringComparison.OrdinalIgnoreCase))
return all[i];
}
if (all.Length == 1 && string.Equals(controlName, "DEFAULT_Txt_Input", StringComparison.OrdinalIgnoreCase))
return all[0];
return null;
}
public static void ConfigurePasswordField(TMP_InputField field)
{
if (field == null)
return;
field.contentType = TMP_InputField.ContentType.Password;
field.inputType = TMP_InputField.InputType.Password;
}
public static TMP_InputField ClonePasswordField(TMP_InputField template, string name, Vector2 anchoredOffset)
{
if (template == null)
return null;
var clone = UnityEngine.Object.Instantiate(template, template.transform.parent);
clone.name = name;
var rt = clone.GetComponent<RectTransform>();
if (rt != null)
rt.anchoredPosition += anchoredOffset;
ConfigurePasswordField(clone);
return clone;
}
public static void ResolveButton(ref Button field, Transform root, params string[] nameHints)
{
if (field != null)
return;
var buttons = root.GetComponentsInChildren<Button>(true);
for (int i = 0; i < buttons.Length; i++)
{
var btn = buttons[i];
if (btn == null)
continue;
string n = btn.name.ToLowerInvariant();
for (int h = 0; h < nameHints.Length; h++)
{
if (n.Contains(nameHints[h]))
{
field = btn;
return;
}
}
}
}
public static void WireConfirmButton(Transform root, Action onConfirm, Button assignedConfirm = null)
{
if (onConfirm == null)
return;
if (assignedConfirm != null)
{
assignedConfirm.onClick.RemoveAllListeners();
assignedConfirm.onClick.AddListener(() => onConfirm());
return;
}
var buttons = root.GetComponentsInChildren<Button>(true);
for (int i = 0; i < buttons.Length; i++)
{
var btn = buttons[i];
if (btn == null || IsCancelButtonName(btn.name))
continue;
string n = btn.name.ToLowerInvariant();
if (n.Contains("confirm") || n.Contains("btn_decide") || n.Contains("btn_confirm") || n == "ok")
{
btn.onClick.RemoveAllListeners();
btn.onClick.AddListener(() => onConfirm());
return;
}
}
}
public static void WireCancelButtons(Transform root, Action onCancel)
{
if (onCancel == null)
return;
var buttons = root.GetComponentsInChildren<Button>(true);
for (int i = 0; i < buttons.Length; i++)
{
var btn = buttons[i];
if (btn == null || !IsCancelButtonName(btn.name))
continue;
btn.onClick.RemoveAllListeners();
btn.onClick.AddListener(() => onCancel());
}
}
static bool IsCancelButtonName(string buttonName)
{
if (string.IsNullOrEmpty(buttonName))
return false;
string n = buttonName.ToLowerInvariant();
if (n.Contains("confirm") || n.Contains("decide") || n == "ok")
return false;
if (n.Contains("add") || n.Contains("minus") || n.Contains("max"))
return false;
return n.Contains("cancel") || n.Contains("btn_exit") || n.Contains("idcanel")
|| n.Contains("btn_close") || n == "close";
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 13cadb01b7f6489da6975a77f3570e37
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,82 @@
using BrewMonster.Network;
using BrewMonster.Scripts;
using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace BrewMonster.UI
{
/// <summary>
/// Warehouse open password (C++ CDlgStoragePW / Win_InputString).
/// Dialog id for <see cref="CECUIManager.ShowUI"/>: <see cref="DialogId"/>.
/// </summary>
public class DlgStoragePW : AUIDialog
{
public const string DialogId = "DlgStoragePW";
[SerializeField] private TMP_InputField passwordInput;
[SerializeField] private Button confirmButton;
[SerializeField] private Button cancelButton;
[SerializeField] private Button closeButton;
public static DlgStoragePW GetDialog()
{
return EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan()?.GetDialog(DialogId) as DlgStoragePW;
}
public override void Awake()
{
base.Awake();
ResolveControls();
WireButtons();
}
public override void OnShowDialogue()
{
base.OnShowDialogue();
WireButtons();
ClearFields();
}
void ResolveControls()
{
passwordInput = DlgStoragePasswordUiHelper.FindInputField(transform, passwordInput, "DEFAULT_Txt_Input");
DlgStoragePasswordUiHelper.ConfigurePasswordField(passwordInput);
DlgStoragePasswordUiHelper.ResolveButton(ref confirmButton, transform, "confirm", "btn_decide", "btn_confirm", "ok");
DlgStoragePasswordUiHelper.ResolveButton(ref cancelButton, transform, "cancel", "idcanel", "btn_cancel", "btn_exit");
DlgStoragePasswordUiHelper.ResolveButton(ref closeButton, transform, "btn_close", "close");
}
void WireButtons()
{
DlgStoragePasswordUiHelper.ResolveButton(ref confirmButton, transform, "confirm", "btn_decide", "btn_confirm", "ok");
DlgStoragePasswordUiHelper.WireConfirmButton(transform, OnConfirm, confirmButton);
DlgStoragePasswordUiHelper.WireCancelButtons(transform, OnCancel);
}
void ClearFields()
{
if (passwordInput != null)
passwordInput.SetTextWithoutNotify(string.Empty);
}
void OnConfirm()
{
string password = passwordInput != null ? passwordInput.text : string.Empty;
UnityGameSession.c2s_CmdNPCSevOpenTrash(password);
CloseDialog();
}
void OnCancel()
{
CECUIManager.Instance?.EndNpcTalkAfterStorageService();
}
void CloseDialog()
{
CECUIManager.Instance?.EndNpcTalkAfterStorageService();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 23e279349ffe4b55ab66cafdf46b383d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ebb0c4a44747f664f9e9071c09b83370
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 54acd8492cecae04aaf22d12852c19b4
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+1
View File
@@ -262,6 +262,7 @@ namespace BrewMonster
invUi?.DismissItemDetail();
CECUIManager.Instance?.HideStorageDialogPair();
CECUIManager.Instance?.HideDialogFromStack("DialogNPC");
if (wasUsingTrash)
UnityGameSession.c2s_CmdCancelAction();
+55 -9
View File
@@ -278,6 +278,57 @@ public class CECUIManager : MonoSingleton<CECUIManager>
dlg.Show(false);
}
/// <summary>Hide a stacked dialog and remove it from <see cref="_uiStack"/>.</summary>
public void HideDialogFromStack(string componentName)
{
if (string.IsNullOrEmpty(componentName))
return;
_uiStack.Remove(componentName);
GetInGameUIMan()?.GetDialog(componentName)?.Show(false);
}
/// <summary>Warehouse password entry / change overlays (must not resurface when closing storage).</summary>
public void HideStoragePasswordDialogs()
{
HideDialogFromStack(DlgStoragePW.DialogId);
HideDialogFromStack(DlgStorageChangePW.DialogId);
}
/// <summary>End NPC service and close talk UI after storage password flows (C++ CloseDialog + EndNPCService).</summary>
public void EndNpcTalkAfterStorageService()
{
HideStoragePasswordDialogs();
var gameUi = GetInGameUIMan();
gameUi?.EndNPCService();
HideDialogFromStack("DialogNPC");
}
void ShowStackTopSkippingStoragePasswordDialogs()
{
var gui = GetInGameUIMan();
if (gui == null)
return;
while (_uiStack.Count > 0)
{
string topId = _uiStack[0];
if (topId == DlgStoragePW.DialogId || topId == DlgStorageChangePW.DialogId)
{
HideDialogFromStack(topId);
continue;
}
var newTop = gui.GetDialog(topId);
if (newTop != null)
{
newTop.Show(true);
newTop.transform.SetAsLastSibling();
}
return;
}
}
public void HideCurrentUIInStack()
{
Pop();
@@ -322,6 +373,8 @@ public class CECUIManager : MonoSingleton<CECUIManager>
if (gui == null)
return;
HideStoragePasswordDialogs();
var storageDlg = gui.GetDialog("EC_StorageUI");
var invDlg = gui.GetDialog("Win_Inventory");
if (storageDlg == null || invDlg == null)
@@ -350,15 +403,8 @@ public class CECUIManager : MonoSingleton<CECUIManager>
gui?.GetDialog("Win_Inventory")?.Show(false);
gui?.GetDialog("EC_StorageUI")?.Show(false);
if (_uiStack.Count > 0)
{
var newTop = gui?.GetDialog(_uiStack[0]);
if (newTop != null)
{
newTop.Show(true);
newTop.transform.SetAsLastSibling();
}
}
HideStoragePasswordDialogs();
ShowStackTopSkippingStoragePasswordDialogs();
}
/// <summary>