Merge branch 'develop' into feature/elseplayer

# Conflicts:
#	Assets/PerfectWorld/Scripts/Network/CSNetwork/GPDataType.cs
#	Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs
#	Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs
This commit is contained in:
Tungdv
2026-03-18 12:54:34 +07:00
175 changed files with 17992 additions and 1722 deletions
+25
View File
@@ -0,0 +1,25 @@
# EditorConfig for Unity Project
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
[*.cs]
indent_style = space
indent_size = 4
end_of_line = crlf
# Unity Editor Only Usage Analyzer
# ERROR (màu đỏ) khi sử dụng code trong #if UNITY_EDITOR từ code không có directive
dotnet_diagnostic.UNITY_EDITOR_ONLY_USAGE.severity = error
# Các rule khác cho Unity
dotnet_analyzer_diagnostic.category-Unity.severity = warning
[*.{asmdef,asmref}]
indent_size = 2
[*.{json,md}]
indent_size = 2
+1 -1
View File
@@ -102,4 +102,4 @@ InitTestScene*.unity*
.idea
# AI Context
claude.md
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d6389bfe41ba69e42991434d87c1319f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+157
View File
@@ -0,0 +1,157 @@
#if UNITY_EDITOR
using System.IO;
using UnityEditor;
using UnityEngine;
/// <summary>
/// Tự động thêm UnityEditorOnlyAnalyzer vào các .csproj files khi Unity generate chúng
/// Analyzer phải ở project root, KHÔNG trong Assets/ để tránh Unity load như runtime assembly
/// </summary>
public class AddAnalyzerPostprocessor : AssetPostprocessor
{
private static string GetAnalyzerPath()
{
string projectRoot = Path.GetDirectoryName(Application.dataPath);
string analyzerPath = Path.Combine(projectRoot, "UnityEditorOnlyAnalyzer/bin/Release/netstandard2.0/UnityEditorOnlyAnalyzer.dll");
return Path.GetFullPath(analyzerPath);
}
/// <summary>
/// Unity gọi method này cho từng .csproj file được generate
/// Return content đã được modify - Unity sẽ ghi content này vào file
/// </summary>
public static string OnGeneratedCSProject(string path, string content)
{
string fileName = Path.GetFileName(path);
// Chỉ thêm vào các runtime assemblies, không thêm vào Editor assemblies
if (fileName != "Assembly-CSharp.csproj" && fileName != "Assembly-CSharp-firstpass.csproj")
{
return content;
}
// Kiểm tra xem đã có analyzer chưa
if (content.Contains("UnityEditorOnlyAnalyzer.dll"))
{
return content;
}
string analyzerPath = GetAnalyzerPath();
// Kiểm tra analyzer có tồn tại không
if (!File.Exists(analyzerPath))
{
Debug.LogWarning($"[UnityEditorOnlyAnalyzer] Analyzer not found at {analyzerPath}. " +
"Please build the analyzer first: cd UnityEditorOnlyAnalyzer && dotnet build -c Release");
return content;
}
// Bug fix 1: Tìm "</Project>" không có dấu cách ở đầu
int lastProjectTag = content.LastIndexOf("</Project>");
if (lastProjectTag < 0)
{
Debug.LogWarning($"[UnityEditorOnlyAnalyzer] Could not find </Project> tag in {fileName}");
return content;
}
// Bug fix 2: Không double-escape backslash trong XML - XML tự động escape
// Chỉ cần đảm bảo path có backslash đúng format Windows
string analyzerInclude = $@" <ItemGroup>
<Analyzer Include=""{analyzerPath}"" />
</ItemGroup>
</Project>";
string modifiedContent = content.Substring(0, lastProjectTag) + analyzerInclude;
Debug.Log($"[UnityEditorOnlyAnalyzer] ✅ Added analyzer to {fileName}");
return modifiedContent;
}
/// <summary>
/// Public method để menu item có thể gọi thủ công
/// </summary>
public static void AddAnalyzerToProjects()
{
// Trigger Unity regenerate .csproj files để OnGeneratedCSProject được gọi
UnityEditorInternal.InternalEditorUtility.OpenFileAtLineExternal("", 0);
AssetDatabase.Refresh();
// Cũng gọi fallback method để đảm bảo
OnGeneratedCSProjectFiles();
}
/// <summary>
/// Backup method: Unity gọi method này sau khi generate tất cả .csproj files
/// Chỉ dùng nếu OnGeneratedCSProject không hoạt động
/// </summary>
private static void OnGeneratedCSProjectFiles()
{
// Fallback: patch files sau khi đã được generate
string projectRoot = Path.GetDirectoryName(Application.dataPath);
string analyzerPath = GetAnalyzerPath();
if (!File.Exists(analyzerPath))
{
Debug.LogWarning($"[UnityEditorOnlyAnalyzer] Analyzer not found at {analyzerPath}. " +
"Please build the analyzer first: cd UnityEditorOnlyAnalyzer && dotnet build -c Release");
return;
}
string[] csprojFiles = Directory.GetFiles(projectRoot, "*.csproj", SearchOption.TopDirectoryOnly);
bool anyModified = false;
foreach (string csprojFile in csprojFiles)
{
string fileName = Path.GetFileName(csprojFile);
if (fileName == "Assembly-CSharp.csproj" || fileName == "Assembly-CSharp-firstpass.csproj")
{
string content = File.ReadAllText(csprojFile);
if (!content.Contains("UnityEditorOnlyAnalyzer.dll"))
{
// Bug fix: Tìm "</Project>" không có dấu cách
int lastProjectTag = content.LastIndexOf("</Project>");
if (lastProjectTag >= 0)
{
string analyzerInclude = $@" <ItemGroup>
<Analyzer Include=""{analyzerPath}"" />
</ItemGroup>
</Project>";
content = content.Substring(0, lastProjectTag) + analyzerInclude;
File.WriteAllText(csprojFile, content);
anyModified = true;
Debug.Log($"[UnityEditorOnlyAnalyzer] ✅ Added analyzer to {fileName} (fallback method)");
}
}
}
}
if (anyModified)
{
Debug.Log("[UnityEditorOnlyAnalyzer] ✅ Analyzer added to .csproj files! Please reload your IDE.");
}
}
}
/// <summary>
/// Menu item để trigger thủ công việc thêm analyzer
/// </summary>
public class UnityEditorOnlyAnalyzerMenu
{
[MenuItem("Tools/Unity Editor Only Analyzer/Add Analyzer to Projects")]
public static void AddAnalyzerManually()
{
AddAnalyzerPostprocessor.AddAnalyzerToProjects();
}
[MenuItem("Tools/Unity Editor Only Analyzer/Regenerate Project Files")]
public static void RegenerateProjectFiles()
{
// Trigger Unity regenerate .csproj files
UnityEditorInternal.InternalEditorUtility.OpenFileAtLineExternal("", 0);
AssetDatabase.Refresh();
Debug.Log("[UnityEditorOnlyAnalyzer] Project files regeneration triggered. Check Console for analyzer messages.");
}
}
#endif
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f4597c784f3bab545bcae652cd8541d9
@@ -50,7 +50,7 @@ ParticleSystem:
ringBufferMode: 0
ringBufferLoopRange: {x: 0, y: 1}
emitterVelocityMode: 1
looping: 1
looping: 0
prewarm: 0
playOnAwake: 1
useUnscaledTime: 0
@@ -4883,7 +4883,7 @@ ParticleSystem:
ringBufferMode: 0
ringBufferLoopRange: {x: 0, y: 1}
emitterVelocityMode: 1
looping: 1
looping: 0
prewarm: 0
playOnAwake: 1
useUnscaledTime: 0
@@ -9774,7 +9774,7 @@ ParticleSystem:
ringBufferMode: 0
ringBufferLoopRange: {x: 0, y: 1}
emitterVelocityMode: 1
looping: 1
looping: 0
prewarm: 0
playOnAwake: 1
useUnscaledTime: 0
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7683706845393114874}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0.365, y: -0.002, z: -0.002}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 0, y: 90, z: 0}
--- !u!33 &2133889840078759797
MeshFilter:
m_ObjectHideFlags: 0
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1062321236472470772}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0.365, y: -0.002, z: -0.002}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 0, y: 90, z: 0}
--- !u!33 &3655013707296471465
MeshFilter:
m_ObjectHideFlags: 0
@@ -26,13 +26,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3330708356810309571}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0.388, y: -0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 0, y: 90, z: 0}
--- !u!33 &666975227472843166
MeshFilter:
m_ObjectHideFlags: 0
@@ -620,7 +620,7 @@ GameObject:
- component: {fileID: 8250962023850685786}
- component: {fileID: 7766051278568089760}
m_Layer: 5
m_Name: ButtonOk
m_Name: ButtonYes
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -927,7 +927,7 @@ MonoBehaviour:
imageProgress: {fileID: 0}
titleText: {fileID: 5031655611580643013}
messageText: {fileID: 7448521238108099750}
okButton: {fileID: 7766051278568089760}
_yesButton: {fileID: 7766051278568089760}
_noButton: {fileID: 7010901635634620631}
_closeButton: {fileID: 482550456836939169}
--- !u!1 &5664175764923475105
@@ -509,7 +509,7 @@ MonoBehaviour:
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 0
m_Interactable: 1
m_TargetGraphic: {fileID: 3492245093881047436}
m_OnClick:
m_PersistentCalls:
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0dd087950038db412bdd07208e3e3dc407a2da4ebecc8fbd49ad2246197aecdb
size 314447
oid sha256:47ff1377fe87865c1bdada70b8f6fee638a20879f70b84b40d62fb978aee203e
size 325051
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8c97935b0995b6688065daf1645d0f26e8b964dd5bf92b6cd66c37c5f97b5586
size 104077
oid sha256:4b854fd255462f21d3b44d27526138345930a673f5c74cd2c1ca261ebd834048
size 106212
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2f24d310ca7b52342ab8380890ad8c0b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,239 @@
using System.Collections.Generic;
using BrewMonster.Network;
using BrewMonster.Scripts.Managers;
using BrewMonster.UI;
using UnityEngine;
namespace BrewMonster.Scripts.Chat
{
public class CECPateText
{
protected List<string> m_aTextStrs;
//protected List<> m_aEmotions;
//protected List<ITEM, ITEM> m_aItems;
//protected EditBoxItemSet m_ItemSet;
// Text item type
public enum ETextType
{
TYPE_TEXT = 0,
TYPE_EMOTION,
TYPE_BOOTHNAME,
}
// Text item
public struct ITEM
{
public int iType; // Text type
public int iIndex; // Index of item
public int iExtX; // Extent
public int iExtY;
public int iLine;
//A3DCOLOR clItem;
public Color clItem;
};
public int SetText(string szText,
bool bIncEmotion,
out string pstrTextConverted,
bool bEllipsis = true,
EC_IvtrItem pIvtrItem = null)
{
// Clear old content
Clear();
pstrTextConverted = null;
if (string.IsNullOrEmpty(szText))
return 0;
CECGameUIMan pGameUI =
EC_Game.GetGameRun().GetUIManager().GetInGameUIMan();
/*string str = pGameUI.AUI_FilterEditboxItem(
szText,
CECGameUIMan.AUI_EditboxItemMaskFilter(1 << (int)enumEICoord)
);
string strName;
A3DCOLOR clrName;
pGameUI.TransformNameColor(pIvtrItem, out strName, out clrName);
str = UnmarshalEditBoxText(str, m_ItemsSet, 0, strName, clrName);
szText = str;
pstrTextConverted = str;
int iAddedChar = 0;
if (!bIncEmotion)
{
int iLen = szText.Length;
if (iLen > m_iMaxLineLen)
{
string sub = szText.Substring(0, m_iMaxLineLen);
if (bEllipsis)
sub += "...";
CreateTextItem(sub, -1, 0);
iAddedChar = m_iMaxLineLen;
}
else
{
CreateTextItem(szText, -1, 0);
iAddedChar = iLen;
}
}
else
{
int i = 0;
int iStart = 0;
int iEnd = 0;
int iLenCnt = 0;
bool bTooLong = false;
int iLine = 0;
while (i < szText.Length)
{
char ch = szText[i];
if (IsEditboxItemCode(ch))
{
if (iEnd > iStart)
CreateTextItem(szText.Substring(iStart, iEnd - iStart), -1, iLine);
EditBoxItemBase pItem = m_ItemsSet.GetItemByChar(ch);
if (pItem != null)
{
if (pItem.GetType() == enumEIEmotion)
{
int nSet = 0;
int nIndex = 0;
UnmarshalEmotionInfo(pItem.GetInfo(), out nSet, out nIndex);
CreateEmotionItem(nSet, nIndex, iLine);
iLenCnt += 2;
}
else
{
string szName = pItem.GetName();
CreateTextItem(szName, -1, iLine, pItem.GetColor());
iLenCnt += szName.Length;
}
}
i++;
iStart = i;
iEnd = i;
goto CheckLength;
}
iEnd++;
i++;
iLenCnt++;
CheckLength:
if (iLenCnt > m_iMaxLineLen)
{
if (iLine + 1 >= m_iMaxLines)
{
bTooLong = true;
break;
}
if (iEnd > iStart)
CreateTextItem(szText.Substring(iStart, iEnd - iStart), -1, iLine);
iStart = i;
iLine++;
iLenCnt = 0;
}
}
iAddedChar = i;
if (iEnd > iStart)
{
if (bTooLong)
{
string strEnd = szText.Substring(iStart, iEnd - iStart);
if (bEllipsis)
strEnd += "...";
CreateTextItem(strEnd, -1, iLine);
}
else
{
CreateTextItem(szText.Substring(iStart, iEnd - iStart), -1, iLine);
}
}
else if (bTooLong)
{
if (bEllipsis)
CreateTextItem("...", -1, iLine);
}
}
// Calculate extent
m_iExtX = 0;
m_iExtY = 0;
int iLineExtX = 0;
int iLastLine = 0;
for (int i = 0; i < m_aItems.GetSize(); i++)
{
ITEM item = m_aItems[i];
if (item.iLine != iLastLine)
{
iLastLine = item.iLine;
if (m_iExtX < iLineExtX)
m_iExtX = iLineExtX;
iLineExtX = item.iExtX;
}
else
{
iLineExtX += item.iExtX;
}
if (m_iExtY < item.iExtY)
m_iExtY = item.iExtY;
}
m_iLines = iLastLine + 1;
if (m_iExtX < iLineExtX)
m_iExtX = iLineExtX;*/
//return iAddedChar;
return 0;
}
public void Clear()
{
m_aTextStrs.Clear();
/*m_aEmotions.Clear();
m_aItems.Clear();
m_ItemsSet.Clear();*/
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f6eb4b59e25704044a88b0055abb0e99
@@ -0,0 +1,26 @@
using BrewMonster.Scripts.Managers;
using CSNetwork;
namespace BrewMonster.Scripts.Chat
{
public abstract partial class CECPlayer : CECObject
{
private CECPateText m_pPateLastWords1;
private CECPateText m_pPateLastWords2;
private CECCounter m_strLastSayCnt;
// Set last said words
public void SetLastSaidWords(string szWords, int nEmotionSet, EC_IvtrItem pItem)
{
if (m_pPateLastWords1 == null || m_pPateLastWords2 == null)
return;
string str = AUICommon.FilterEmotionSet(szWords, nEmotionSet);
szWords = str;
m_pPateLastWords1.SetText(szWords, true , out var newStr,true, pItem);
m_pPateLastWords2.Clear();
m_strLastSayCnt.Reset();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2c109cbbaf8d4ce19a93990a5f5883f6
timeCreated: 1772699598
@@ -0,0 +1,254 @@
using System;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using BrewMonster.Network;
using BrewMonster.Scripts.Managers;
using CSNetwork;
using CSNetwork.GPDataType;
namespace BrewMonster.Scripts.Chat
{
struct chat_policy_parameter
{
short cmd_id;
int parameter_mask;
//if(parameter_mask & CHAT_PARAMETER_ROLEID) 1 int insert here
//if(parameter_mask & CHAT_PARAMETER_LOCALVAL0) 1 int insert here
//if(parameter_m ask & CHAT_PARAMETER_LOCALVAL1) 1 int insert here
//if(parameter_mask & CHAT_PARAMETER_LOCALVAL2) 1 int insert here
};
public struct chat_item_base
{
public short cmd_id;
}
public struct chat_equip_item
{
public short cmd_id;
public char where;
public short index;
}
struct chat_generalcard_collection
{
public short cmd_id;
public int card_id;
};
public static class CHAT_S2C
{
public struct chat_equip_item
{
public short cmd_id;
public int type;
public int expire_date;
public int proc_type;
public ushort content_length;
public byte[] content;
public int LenghtHeader()
{
return Marshal.SizeOf<short>() + (Marshal.SizeOf<int>() * 3) + Marshal.SizeOf<ushort>();
}
}
public enum EChatS2CCommand : short
{
CHAT_EQUIP_ITEM,
CHAT_GENERALCARD_COLLECTION,
CHAT_POLICYCHAT_PARAMETER,
}
public static EC_IvtrItem CreateChatItem(Octets data)
{
EC_IvtrItem pIvtrItem = null;
if (data.Size > 0)
{
chat_item_base pInfo = GPDataTypeHelper.FromBytes<chat_item_base>(data.ByteArray);
if (pInfo.cmd_id == (short)EChatS2CCommand.CHAT_EQUIP_ITEM)
{
chat_equip_item pItemInfo = default;
var sz = pItemInfo.LenghtHeader();
if (data.Size >= sz && sz + pItemInfo.content_length == data.Size)
{
if (pItemInfo.cmd_id == (short)EChatS2CCommand.CHAT_EQUIP_ITEM)
{
pIvtrItem = EC_IvtrItem.CreateItem(pItemInfo.type, pItemInfo.expire_date, 1);
if (pIvtrItem != null)
{
pIvtrItem.SetProcType(pItemInfo.proc_type);
pIvtrItem.SetItemInfo(pItemInfo.content, pItemInfo.content_length);
}
}
}
}
else if (pInfo.cmd_id == (short)EChatS2CCommand.CHAT_GENERALCARD_COLLECTION)
{
chat_generalcard_collection pItemInfo =
GPDataTypeHelper.FromBytes<chat_generalcard_collection>(data.ByteArray);
if (data.Size > Marshal.SizeOf<chat_generalcard_collection>())
{
if (pItemInfo.cmd_id == (short)EChatS2CCommand.CHAT_GENERALCARD_COLLECTION)
{
pIvtrItem = EC_IvtrItem.CreateItem(pItemInfo.card_id, 0, 1);
if (pIvtrItem != null)
{
pIvtrItem.GetDetailDataFromLocal();
}
}
}
}
}
return pIvtrItem;
}
[System.Flags]
public enum ChatParameterMask
{
CHAT_PARAMETER_ROLEID = 0x00000001,
CHAT_PARAMETER_LOCALVAL0 = 0x00000002,
CHAT_PARAMETER_LOCALVAL1 = 0x00000004,
CHAT_PARAMETER_LOCALVAL2 = 0x00000008
}
public class PolicyChatParameter
{
public int role_id = -1;
public string name = string.Empty;
public ChatParameterMask parameter_mask = 0;
private int localval_0 = -1;
private int localval_1 = -1;
private int localval_2 = -1;
EC_Game g_pGame;
public bool HasRoleID()
{
return (parameter_mask & ChatParameterMask.CHAT_PARAMETER_ROLEID) != 0;
}
public bool HasLocalValue(int valueNum)
{
switch (valueNum)
{
case 0:
return (parameter_mask & ChatParameterMask.CHAT_PARAMETER_LOCALVAL0) != 0;
case 1:
return (parameter_mask & ChatParameterMask.CHAT_PARAMETER_LOCALVAL1) != 0;
case 2:
return (parameter_mask & ChatParameterMask.CHAT_PARAMETER_LOCALVAL2) != 0;
default:
return false;
}
}
public void GetNameFromServer()
{
if (role_id != -1 && HasRoleID())
{
int[] arr = new int[1] { role_id };
UnityGameSession.Instance.GameSession.CmdCache.SendGetPlayerBriefInfo(1, arr, 1);
}
}
public void SetLocalValue(int localValue, int id)
{
switch (id)
{
case 0:
localval_0 = localValue;
parameter_mask |= ChatParameterMask.CHAT_PARAMETER_LOCALVAL0;
break;
case 1:
localval_1 = localValue;
parameter_mask |= ChatParameterMask.CHAT_PARAMETER_LOCALVAL1;
break;
case 2:
localval_2 = localValue;
parameter_mask |= ChatParameterMask.CHAT_PARAMETER_LOCALVAL2;
break;
}
}
public bool TryGetLocalValue(int localVariableID, out int localVariable)
{
localVariable = -1;
if (!HasLocalValue(localVariableID))
return false;
switch (localVariableID)
{
case 0: localVariable = localval_0; break;
case 1: localVariable = localval_1; break;
case 2: localVariable = localval_2; break;
}
return true;
}
public bool IsNameReady()
{
return HasRoleID() ? !string.IsNullOrEmpty(name) : true;
}
}
public static PolicyChatParameter CreatPolicyChatParameter(Octets data)
{
PolicyChatParameter result = null;
if (data.Size >= Marshal.SizeOf(typeof(chat_item_base)))
{
chat_item_base pInfo = GPDataTypeHelper.FromBytes<chat_item_base>(data.ByteArray);
if (pInfo.cmd_id == (short)EChatS2CCommand.CHAT_POLICYCHAT_PARAMETER)
{
if (data.Size >= Marshal.SizeOf(typeof(chat_policy_parameter)))
{
Byte pData = GPDataTypeHelper.FromBytes<Byte>(data.ByteArray,
Marshal.SizeOf(typeof(chat_policy_parameter)));
result = new PolicyChatParameter();
// Todo: Check logic
result.parameter_mask =
GPDataTypeHelper.FromBytes<ChatParameterMask>(data.ByteArray, sizeof(short));
/*int paraMask = result.parameter_mask;
if (paraMask & ChatParameterMask.CHAT_PARAMETER_ROLEID){
if (pData + sizeof(int) > data.end()){
return null;
}
result->SetRoleId(*(int*)pData);
pData += sizeof(int);
}
if (paraMask & ChatParameterMask.CHAT_PARAMETER_LOCALVAL0){
if (pData + sizeof(int) > data.end()){
return null;
}
result->SetLocaValue(*(int*)pData, 0);
pData += sizeof(int);
}
if (paraMask & ChatParameterMask.CHAT_PARAMETER_LOCALVAL1){
if (pData + sizeof(int) > data.end()){
return null;
}
result.SetLocaValue((int)pData, 1);
pData += sizeof(int);
}
if (paraMask & ChatParameterMask.CHAT_PARAMETER_LOCALVAL2){
if (pData + sizeof(int) > data.end()){
return null;
}
// Check logic
result.SetLocalValue((int)pData, 2);
pData += sizeof(int);
}*/
}
}
}
return result;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f7564fa8d29f58c40a1aa0425fc0c046
@@ -0,0 +1,243 @@
using System;
using System.Collections.Generic;
using BrewMonster.Network;
using BrewMonster.Scripts.UI;
using CSNetwork.GPDataType;
using CSNetwork.Protocols;
namespace BrewMonster.Scripts.Chat
{
public static class Chat_GameSession
{
private static List<Protocol> m_aPendingProtocols = new();
private static List<int> m_aPendingPlayers = new();
public static void AddElemForPendingProtocols(Protocol p)
{
m_aPendingProtocols.Add(p);
}
// Add one player's id to a buffer in order to get his name later
public static void AddChatPlayerID(int id)
{
if (EC_Game.GetGameRun().GetPlayerName(id, false) != null)
{
m_aPendingPlayers.Add(id);
}
}
public static bool ShouldBlockByLevel(chatmessage p)
{
int levelBlock = EC_Game.GetConfigs().GetBlackListSettings().levelBlock;
if (p.Srclevel > 0 && p.Srclevel < levelBlock)
{
if ((ChatChannel)p.Channel is ChatChannel.GP_CHAT_LOCAL
or ChatChannel.GP_CHAT_WHISPER
or ChatChannel.GP_CHAT_TRADE)
{
if (!EC_Game.GetGameRun().GetHostPlayer().IsOmitBlocking(p.Srcroleid))
{
// should be filted by level
return true;
}
}
}
return false;
}
public static bool PolicyResolver(Protocol pProtocol, chatmessage p, ref string strTemp)
{
if (IsPolicyChat(p))
{
// Todo: check logic
CHAT_S2C.PolicyChatParameter pPolicyChatPara = CHAT_S2C.CreatPolicyChatParameter(p.Data);
if (pPolicyChatPara != null && p.Data.Size > 0)
{
strTemp = ("???");
}
else
{
if (pPolicyChatPara != null && !pPolicyChatPara.IsNameReady())
{
pPolicyChatPara.GetNameFromServer();
m_aPendingProtocols.Add(pProtocol);
return false;
}
strTemp = CECUIHelper.PolicySpecialCharReplace(strTemp, pPolicyChatPara);
if (CanFormatCoordText(p))
{
//strTemp = CECUIHelper.FormatCoordText(strTemp);
}
}
//int strLen = strTemp.GetLength();
//wcsncpy(szMsg, strTemp, strLen);
//szMsg[strLen] = 0;
}
else
{
//AUI_ConvertChatString(strTemp, szMsg, false);
}
return true;
}
/*private void AUI_ConvertChatString(string pszChat, string pszConv, bool bName)
{
int i, nLen = 0;
if (pszChat != null || pszConv != null)
return;
pszConv[0] = 0;
for( i = 0; i < (int)a_strlen(pszChat); i++ )
{
if( pszChat[i] == '^' )
{
pszConv[nLen] = '^';
pszConv[nLen + 1] = '^';
nLen += 2;
}
else if( pszChat[i] == '&' )
{
pszConv[nLen] = '^';
pszConv[nLen + 1] = '&';
nLen += 2;
}
else
{
pszConv[nLen] = pszChat[i];
nLen++;
}
}
pszConv[nLen] = 0;
}*/
private static bool IsPolicyChat(chatmessage p)
{
bool bOK = false;
switch (p.Channel)
{
case (byte)ChatChannel.GP_CHAT_LOCAL: // ÃæÏò¸Ã NPC ¿É¼ûÓòÖÐËùÓÐÍæ¼Ò
if (p.Srcroleid == 0 || // £¨²ßÂÔº°»°Öбê¼Ç $A £©
ISNPCID(p.Srcroleid))
{
// £¨²ßÂÔº°»°ÖÐÎÞ±ê¼Ç £©
bOK = true;
}
break;
case (byte)ChatChannel.GP_CHAT_BATTLE: // ÃæÏò³ÇÕ½¸±±¾Ö¸¶¨ÕóÓª»òÈ«²¿Íæ¼Òº°»°£¨²ßÂÔº°»°Öбê¼Ç $F¡¢$T £©
if (p.Srcroleid == 0)
{
// Ϊ·ÀÒÔºóÓб仯£¬Ôö¼Ó´ËÅжÏ
bOK = true;
}
break;
case (byte)ChatChannel.GP_CHAT_BROADCAST: // ÃæÏòÈ«ÌåÔÚÏßÍæ¼Ò£¨²ßÂÔº°»°Öбê¼Ç $S £©
if (p.Srcroleid == 0)
{
bOK = true;
}
break;
case (byte)ChatChannel.GP_CHAT_INSTANCE
: // ÃæÏò¸±±¾ÖÐÍæ¼Ò£¨²ßÂÔº°»°Öбê¼Ç $I £©£¨ $X ±ê¼Çʱ p->srcroleid == 1£¬´Ëʱֻ³öÏÖÔÚÆÁÄ»ÖÐÑ룩
if (p.Srcroleid == 0 || p.Srcroleid == 1)
{
bOK = true;
}
break;
}
return bOK;
}
static bool ISNPCID(int id)
{
uint uid = (uint)id;
return (uid & 0x80000000) != 0 &&
(uid & 0x40000000) == 0;
}
static bool CanFormatCoordText(chatmessage p)
{
bool bOK = false;
switch (p.Channel)
{
case (byte)ChatChannel.GP_CHAT_LOCAL: // ÃæÏò¸Ã NPC ¿É¼ûÓòÖÐËùÓÐÍæ¼Ò
if (p.Srcroleid == 0 || // £¨²ßÂÔº°»°Öбê¼Ç $A £©
ISNPCID(p.Srcroleid))
{
// £¨²ßÂÔº°»°ÖÐÎÞ±ê¼Ç £©
bOK = true;
}
break;
case (byte)ChatChannel.GP_CHAT_BATTLE: // ÃæÏò³ÇÕ½¸±±¾Ö¸¶¨ÕóÓª»òÈ«²¿Íæ¼Òº°»°£¨²ßÂÔº°»°Öбê¼Ç $F¡¢$T £©
if (p.Srcroleid == 0)
{
// Ϊ·ÀÒÔºóÓб仯£¬Ôö¼Ó´ËÅжÏ
bOK = true;
}
break;
case (byte)ChatChannel.GP_CHAT_BROADCAST: // ÃæÏòÈ«ÌåÔÚÏßÍæ¼Ò£¨²ßÂÔº°»°Öбê¼Ç $S £©
if (p.Srcroleid == 0)
{
bOK = true;
}
break;
case (byte)ChatChannel.GP_CHAT_INSTANCE: //p->srcroleid == 1 ʱ²»½øÐÐ×ø±êµÄÌæ»»
if (p.Srcroleid == 0)
{
bOK = true;
}
break;
}
return bOK;
}
public class AUICTranslate
{
protected string m_AString = string.Empty;
protected string m_AWString = string.Empty;
public AUICTranslate()
{
}
public string Translate(string str)
{
// In original C++ this likely performs UI charset translation.
// Here we simply return the same string or store it.
m_AString = str;
return m_AString;
}
public string Translate(ReadOnlySpan<char> str)
{
m_AWString = new string(str);
return m_AWString;
}
public string ReverseTranslate(string str)
{
// Reverse translation placeholder
m_AString = str;
return m_AString;
}
public string ReverseTranslate(ReadOnlySpan<char> str)
{
m_AWString = new string(str);
return m_AWString;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7c774ca3c7d345e4ea0fbb7397bf1d88
@@ -0,0 +1,17 @@
using UnityEngine;
namespace BrewMonster.Scripts.Chat
{
public static class Chat_Helper
{
public static Color32 ToColor32(uint c)
{
byte a = (byte)((c >> 24) & 0xFF);
byte r = (byte)((c >> 16) & 0xFF);
byte g = (byte)((c >> 8) & 0xFF);
byte b = (byte)(c & 0xFF);
return new Color32(r, g, b, a);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5ef4426a7625eb74aa802808e2223421
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3d2c00e6fd97fce4c8061a0c54327a5f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using TMPro;
using UnityEngine;
namespace BrewMonster.Scripts.ChatUI
{
public class ChatMessageView : MonoBehaviour
{
public TextMeshProUGUI messageText;
public void Bind(string message)
{
messageText.text = message;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 179d32c667fc2f641bdcb7afb18046b9
@@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using CSNetwork;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.UI;
namespace BrewMonster.Scripts.ChatUI
{
public class ChatPanelUI : MonoBehaviour
{
[Header("UI")] public ScrollRect scrollRect;
public GameObject chatPanelUIGO;
public RectTransform content;
public ChatMessageView messagePrefab;
//public GameObject newMessageIndicator;
[Header("Config")] public int maxVisibleMessages = 30;
public int maxStoredMessages = 2000;
private List<string> _messages = new();
private List<ChatMessageView> _visibleViews = new();
private ObjectPool<ChatMessageView> _pool;
private bool _userAtBottom = true;
void Awake()
{
EventBus.Subscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
_pool = new ObjectPool<ChatMessageView>(
CreateItem,
OnGetItem,
OnReleaseItem,
OnDestroyItem,
false,
10,
100
);
scrollRect.onValueChanged.AddListener(OnScrollChanged);
}
private void OnDestroy()
{
EventBus.Unsubscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
}
private void OnChatMessageReceived(GameSession.ChatMessageEvent x)
{
ChatThreadDispatcher.Instance.Post(() =>
{
AddMessage(x.context);
});
}
ChatMessageView CreateItem()
{
var item = Instantiate(messagePrefab);
item.transform.SetParent(content, false);
return item;
}
void OnGetItem(ChatMessageView item)
{
item.gameObject.SetActive(true);
}
void OnReleaseItem(ChatMessageView item)
{
item.gameObject.SetActive(false);
}
void OnDestroyItem(ChatMessageView item)
{
Destroy(item.gameObject);
}
void OnScrollChanged(Vector2 pos)
{
_userAtBottom = scrollRect.verticalNormalizedPosition <= 0.001f;
if (_userAtBottom)
{
//newMessageIndicator.SetActive(false);
}
}
bool IsAtBottom()
{
return scrollRect.verticalNormalizedPosition <= 0.001f;
}
public void AddMessage(string msg)
{
_messages.Add(msg);
if (_messages.Count > maxStoredMessages)
_messages.RemoveAt(0);
if (!chatPanelUIGO.activeSelf)
return;
AddMessageView(msg);
if (_userAtBottom)
ScrollToBottom();
}
void AddMessageView(string msg)
{
var view = _pool.Get();
view.transform.SetParent(content, false);
view.transform.SetAsLastSibling();
view.Bind(msg);
_visibleViews.Add(view);
if (_visibleViews.Count > maxVisibleMessages)
{
var old = _visibleViews[0];
_visibleViews.RemoveAt(0);
_pool.Release(old);
}
Canvas.ForceUpdateCanvases();
}
void RefreshVisible()
{
foreach (var view in _visibleViews)
_pool.Release(view);
_visibleViews.Clear();
int start = Mathf.Max(0, _messages.Count - maxVisibleMessages);
for (int i = start; i < _messages.Count; i++)
{
var view = _pool.Get();
view.transform.SetParent(content, false);
view.transform.SetAsLastSibling();
view.Bind(_messages[i]);
_visibleViews.Add(view);
}
Canvas.ForceUpdateCanvases();
ScrollToBottom();
}
public void ScrollToBottom()
{
Canvas.ForceUpdateCanvases();
scrollRect.verticalNormalizedPosition = 0f;
//newMessageIndicator.SetActive(false);
}
public void ClearChat()
{
foreach (var view in _visibleViews)
_pool.Release(view);
_visibleViews.Clear();
_messages.Clear();
}
public void OnHandlerChatButton()
{
bool open = !chatPanelUIGO.activeSelf;
chatPanelUIGO.SetActive(open);
if (open)
RefreshVisible();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5790dcd71bafcec4697f10b5366bec2c
@@ -0,0 +1,47 @@
using System;
using System.Collections.Concurrent;
using UnityEngine;
namespace BrewMonster.Scripts.ChatUI
{
/// <summary>
/// Responsible for switching chat messages from background threads to Unity main thread.
/// Only handles chat related actions.
/// </summary>
public class ChatThreadDispatcher : MonoSingleton<ChatThreadDispatcher>
{
private static readonly ConcurrentQueue<Action> _queue = new ConcurrentQueue<Action>();
protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(gameObject);
}
/// <summary>
/// Called from ANY thread (network thread safe)
/// </summary>
public void Post(Action action)
{
if (action == null)
return;
_queue.Enqueue(action);
}
private void Update()
{
while (_queue.TryDequeue(out var action))
{
try
{
action?.Invoke();
}
catch (Exception e)
{
Debug.LogError($"ChatThreadDispatcher error: {e}");
}
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 00392315a7ee47e7a9527eda504fb312
timeCreated: 1773310087
@@ -270,7 +270,7 @@ namespace BrewMonster.Common
{
m_UseItemCmdList.Clear();
// 重置 C2S 命令计时器
// C2S ʱ
m_CounterMap[(int)CommandID.USE_ITEM].Reset(true);
m_EnterSanctuaryList.Clear();
@@ -280,7 +280,7 @@ namespace BrewMonster.Common
m_PresentInfoList.Clear();
m_CounterMap[(int)CommandID.PLAYER_GIVE_PRESENT].Reset(true);
// 重置协议计时器
// Эʱ
m_GetPlayerBriefInfoList.Clear();
m_CounterMap2[(int)ProtocolType.PROTOCOL_GETPLAYERBRIEFINFO].Reset(true);
@@ -377,13 +377,13 @@ namespace BrewMonster.Common
getplayerbriefinfo p = m_GetPlayerBriefInfoList[0];
if (p.Playerlist.Count != 0)
{
// 获取第一个玩家id并向服务器发送协议
// ȡһidЭ
getplayerbriefinfo temp = p;
temp.Playerlist.Clear();
temp.Playerlist.Add(p.Playerlist[0]);
UnityGameSession.Instance.GameSession.SendProtocol(temp);
// 从列表中清除
// б
p.Playerlist.Remove(p.Playerlist[0]);
}
@@ -670,12 +670,12 @@ namespace BrewMonster.Common
}
// Send protocols ...
void SendGetPlayerBriefInfo(int iNumPlayer, int[] aIDs, int iReason)
public void SendGetPlayerBriefInfo(int iNumPlayer, int[] aIDs, int iReason)
{
if (iNumPlayer == 0 || aIDs == null || aIDs.Length == 0)
return;
// 1.合并添加到列表
// 1.ϲӵб
getplayerbriefinfo p = new getplayerbriefinfo();
p.Roleid = EC_Game.GetGameRun().GetHostPlayer().GetCharacterID();
p.Reason = (byte)iReason;
@@ -687,7 +687,7 @@ namespace BrewMonster.Common
if (p.Playerlist.Count > 0)
m_GetPlayerBriefInfoList.Add(p);
// 2.检查并发送
// 2.
SendCachedGetPlayerBriefInfo();
}
@@ -124,3 +124,5 @@ namespace BrewMonster.Scripts
}
}
}
/// d 2000 la kn
/// d 1988 + so tien
@@ -4,6 +4,11 @@ using UnityEngine;
public static class GameConstants
{
public static int NUM_MAGICCLASS = 5;
public static int ARMOR_RUIN_SPEED = -25;
public static int WEAPON_RUIN_SPEED = -2;
public static float PLAYER_PRICE_SCALE = 1.0f;
public static int ENDURANCE_SCALE = 100;
}
public struct ROLEBASICPROP
@@ -2,10 +2,6 @@ namespace BrewMonster.Scripts
{
public class InventoryConst
{
// Equipment endurance scale
public const int ENDURANCE_SCALE = 100;
// NUM_MAGICCLASS
public const int NUM_MAGICCLASS = 5;
// Index of item in equipment inventory
public const int EQUIPIVTR_WEAPON = 0;
public const int EQUIPIVTR_HEAD = 1;
@@ -316,8 +316,8 @@ namespace BrewMonster.Network
}
public static void update_require_data(ref prerequisition require)
{
require.durability *= BrewMonster.Scripts.InventoryConst.ENDURANCE_SCALE;
require.max_durability *= BrewMonster.Scripts.InventoryConst.ENDURANCE_SCALE;
require.durability *= GameConstants.ENDURANCE_SCALE;
require.max_durability *= GameConstants.ENDURANCE_SCALE;
}
public static void set_to_classid(DATA_TYPE type, byte[] data, int major_type)
{
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -146,6 +146,7 @@ namespace BrewMonster
pEvent.SetDelay(dwDelayTime);
pEvent.SetReverse(bReverse);
pEvent.SetParam(param);
BMLogger.LogError("[HoangDev] bTraceTarget=" + bTraceTarget);
pEvent.SetTraceTarget(bTraceTarget);
pEvent.SetModifier(dwModifier);
pEvent.SetIsCluster(bCluster);
@@ -503,4 +504,4 @@ namespace BrewMonster
public bool bGfxDisableCamShake;
public bool bHostECMCreatedByGfx;
};
}
}
@@ -271,7 +271,6 @@ namespace BrewMonster
if (m_enumState == GfxSkillEventState.enumHit && m_bTraceTarget)
{
UpdateHitGfxTransform();
// Check if hit GFX has been destroyed (3 second lifetime) or target is destroyed
// 检查命中特效是否已被销毁(3秒生命周期)或目标是否已销毁
if (m_hitGfxInstance == null || (!m_bTargetExist && m_nTargetID != 0))
@@ -296,7 +295,26 @@ namespace BrewMonster
protected override void HitTarget(Vector3 vTarget)
{
base.HitTarget(vTarget);
DestroyFlyGfx();
// Only destroy fly GFX if NOT tracing target
// If tracing target, fly GFX will be cleaned up when buff expires
// 只有在不跟踪目标时才销毁飞行特效
// 如果跟踪目标,飞行特效将在buff过期时清理
if (!m_bTraceTarget)
{
DestroyFlyGfx();
}
else
{
// If fly GFX exists and m_bTraceTarget is true, add to tracking list
// 如果飞行特效存在且m_bTraceTarget为true,添加到跟踪列表
if (m_flyGfxInstance != null)
{
SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_flyGfxInstance, 0); // Skill ID not available, use 0
BMLogger.Log($"[TRACE_TARGET_GFX] HitTarget: Added fly GFX to trace target list (m_bTraceTarget=true)");
}
}
SpawnHitGfx(vTarget);
// TODO Phase 2: Special hit effects (rune, critical, nullity)
@@ -333,6 +351,13 @@ namespace BrewMonster
m_flyGfxInstance = GameObject.Instantiate(prefab, pos, prefab.transform.rotation);
// If m_bTraceTarget is true, add to tracking list when spawned
// 如果m_bTraceTarget为true,在生成时添加到跟踪列表
if (m_bTraceTarget)
{
SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_flyGfxInstance, 0); // Skill ID not available, use 0
BMLogger.Log($"[TRACE_TARGET_GFX] SpawnFlyGfx: Added fly GFX to trace target list (m_bTraceTarget=true)");
}
}
/// <summary>
@@ -401,7 +426,8 @@ namespace BrewMonster
// 这与C++逻辑匹配:当投射物击中地面(无目标)时使用m_szHitGrndGfx
bool bTargetExists = m_bTargetExist && m_nTargetID != 0;
GameObject prefab = bTargetExists ? m_pComposer.GetHitGFX() : m_pComposer.GetHitGrdGFX();
BMLogger.LogError("HitGfx : " + m_pComposer.hitGfxName);
//BMLogger.LogError("bTargetExists : " + bTargetExists);
//BMLogger.LogError("HitGfx : " + m_pComposer.hitGfxName);
if (prefab == null)
{
@@ -429,9 +455,21 @@ namespace BrewMonster
m_hitGfxInstance = GameObject.Instantiate(prefab, vTarget, prefab.transform.rotation);
// Destroy hit GFX after 3 seconds (unless m_bTraceTarget is true, then it follows target until destroyed)
// 3秒后销毁命中特效(除非m_bTraceTarget为true否则它会跟随目标直到被销毁)
//GameObject.Destroy(m_hitGfxInstance, 3.0f);
// If m_bTraceTarget is true, add to tracking list (don't auto-destroy)
// 如果m_bTraceTarget为true添加到跟踪列表(不自动销毁)
if (m_bTraceTarget)
{
SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_hitGfxInstance, 0); // Skill ID not available, use 0
//BMLogger.Log($"[TRACE_TARGET_GFX] SpawnHitGfx: Added hit GFX to trace target list (m_bTraceTarget=true)");
}
else
{
// Destroy hit GFX after 5 seconds (unless m_bTraceTarget is true, then it follows target until destroyed)
// 5秒后销毁命中特效(除非m_bTraceTarget为true,否则它会跟随目标直到被销毁)
//HIT_GFX_MAX_TIMESPAN 5000
//BMLogger.Log($"[TRACE_TARGET_GFX] SpawnHitGfx: GameObject.Destroy(m_hitGfxInstance, 5.0f);");
GameObject.Destroy(m_hitGfxInstance, 5.0f);
}
}
/// <summary>
@@ -440,10 +478,28 @@ namespace BrewMonster
/// </summary>
public new void Resume()
{
DestroyFlyGfx();
// Hit GFX is auto-destroyed by Unity's Destroy timer, don't null it here
// 命中特效由Unity的Destroy计时器自动销毁,不在此处置null
m_hitGfxInstance = null;
// Don't destroy GFX if it's in trace target list
// It will be cleaned up when buff expires
// 如果GFX在跟踪目标列表中,不要销毁它
// 它将在buff过期时清理
if (m_flyGfxInstance != null)
{
if (SkillGfxMan.InstanceSub != null && !SkillGfxMan.InstanceSub.IsTraceTargetGfx(m_flyGfxInstance))
{
DestroyFlyGfx();
}
}
if (m_hitGfxInstance != null)
{
if (SkillGfxMan.InstanceSub != null && !SkillGfxMan.InstanceSub.IsTraceTargetGfx(m_hitGfxInstance))
{
// Hit GFX is auto-destroyed by Unity's Destroy timer, don't null it here
// 命中特效由Unity的Destroy计时器自动销毁,不在此处置null
m_hitGfxInstance = null;
}
}
base.Resume();
}
@@ -878,6 +934,13 @@ namespace BrewMonster
protected EC_ManPlayer m_pPlayerMan;
protected CECNPCMan m_pNPCMan;
// Track GFX instances that have m_bTraceTarget = true
// These are typically buff-related trail effects that persist until buff expires
// 跟踪具有m_bTraceTarget = true的GFX实例
// 这些通常是持续到buff结束的buff相关轨迹效果
private List<GameObject> m_TraceTargetGfxList = new List<GameObject>();
private Dictionary<GameObject, int> m_TraceTargetGfxSkillMap = new Dictionary<GameObject, int>();
public SkillGfxMan(CECGameRun pGameRun)
{
m_EventLst = new LinkedList<CECSkillGfxEvent>();
@@ -971,6 +1034,110 @@ namespace BrewMonster
{
m_FreeLst[i].Clear();
}
// Clean up trace target GFX
// 清理跟踪目标GFX
RemoveAllTraceTargetGfx();
}
/// <summary>
/// Add a GFX instance to trace target tracking list
/// 将GFX实例添加到跟踪目标跟踪列表
/// </summary>
public void AddTraceTargetGfx(GameObject gfxInstance, int skillId)
{
if (gfxInstance == null) return;
if (!m_TraceTargetGfxList.Contains(gfxInstance))
{
m_TraceTargetGfxList.Add(gfxInstance);
m_TraceTargetGfxSkillMap[gfxInstance] = skillId;
BMLogger.Log($"[TRACE_TARGET_GFX] Added GFX for skill {skillId}, total tracked: {m_TraceTargetGfxList.Count}");
}
}
/// <summary>
/// Remove all trace target GFX (called when buff states update)
/// 移除所有跟踪目标GFX(在buff状态更新时调用)
/// </summary>
public void RemoveAllTraceTargetGfx()
{
BMLogger.Log($"[TRACE_TARGET_GFX] Removing {m_TraceTargetGfxList.Count} trace target GFX");
foreach (GameObject gfx in m_TraceTargetGfxList)
{
if (gfx != null)
{
GameObject.Destroy(gfx);
}
}
m_TraceTargetGfxList.Clear();
m_TraceTargetGfxSkillMap.Clear();
}
/// <summary>
/// Remove trace target GFX for specific skill
/// 移除特定技能的跟踪目标GFX
/// </summary>
public void RemoveTraceTargetGfxForSkill(int skillId)
{
List<GameObject> toRemove = new List<GameObject>();
foreach (var kvp in m_TraceTargetGfxSkillMap)
{
if (kvp.Value == skillId)
{
toRemove.Add(kvp.Key);
}
}
foreach (GameObject gfx in toRemove)
{
if (gfx != null)
{
GameObject.Destroy(gfx);
}
m_TraceTargetGfxList.Remove(gfx);
m_TraceTargetGfxSkillMap.Remove(gfx);
}
if (toRemove.Count > 0)
{
BMLogger.Log($"[TRACE_TARGET_GFX] Removed {toRemove.Count} GFX for skill {skillId}");
}
}
/// <summary>
/// Check if a GFX instance is tracked as trace target GFX
/// 检查GFX实例是否被跟踪为跟踪目标GFX
/// </summary>
public bool IsTraceTargetGfx(GameObject gfx)
{
return m_TraceTargetGfxList.Contains(gfx);
}
/// <summary>
/// Clean up null references (GFX destroyed elsewhere)
/// 清理空引用(在其他地方销毁的GFX)
/// </summary>
public void CleanupTraceTargetGfx()
{
m_TraceTargetGfxList.RemoveAll(gfx => gfx == null);
List<GameObject> nullKeys = new List<GameObject>();
foreach (var kvp in m_TraceTargetGfxSkillMap)
{
if (kvp.Key == null)
{
nullKeys.Add(kvp.Key);
}
}
foreach (var key in nullKeys)
{
m_TraceTargetGfxSkillMap.Remove(key);
}
}
/// <summary>
@@ -12,13 +12,17 @@ namespace BrewMonster
public int m_nLev;
public int m_nMemNum;
public int GetLevel() { return m_nLev; }
public int GetLevel()
{
return m_nLev;
}
}
public class CECFactionMan
{
Dictionary<uint, Faction_Info> m_FactionMap;
public List<int> m_alliance = new List<int>();
public Faction_Info GetFaction(uint uId, bool bRequestFromServer)
{
if (!m_FactionMap.TryGetValue(uId, out var it))
@@ -30,6 +34,7 @@ namespace BrewMonster
return it;
}
public bool IsFactionAlliance(int fid)
{
if (fid == 0)
@@ -43,5 +48,35 @@ namespace BrewMonster
return false;
}
private Dictionary<int, Faction_Mem_Info> m_MemMap;
public Faction_Mem_Info GetMember(int roleId)
{
if (m_MemMap.TryGetValue(roleId, out var member))
return member;
return null;
}
}
public class Faction_Mem_Info
{
public int RoleId { get; set; }
public int FRoleId { get; set; }
public int Level { get; set; }
public int Profession { get; set; }
public byte OnlineStatus { get; set; }
public byte Gender { get; set; }
public int LoginTime { get; set; }
public string Name { get; set; } = string.Empty;
public string NickName { get; set; } = string.Empty;
public int Contrib { get; set; }
public bool DelayExpel { get; set; }
public uint ExpelEndTime { get; set; }
public int Reputation { get; set; }
public int ReincarnationTimes { get; set; }
public bool IsOnline => OnlineStatus != 0;
}
}
@@ -0,0 +1,251 @@
using System.Collections.Generic;
using BrewMonster.Network;
namespace BrewMonster
{
public class CECFriendMan
{
// =========================
// Nested Types
// =========================
public struct Friend
{
public int Id;
public int Profession;
public int GroupId;
public byte Status;
public int Level;
public int AreaId;
public string Name;
public bool IsGameOnline() => IsGameOnline(Status);
public bool IsGTOnline() => IsGTOnline(Status);
public static bool IsGameOnline(byte s)
{
// TODO: implement đúng theo logic C++
return (s & 0x01) != 0;
}
public static bool IsGTOnline(byte s)
{
// TODO: implement đúng theo logic C++
return (s & 0x02) != 0;
}
}
public class FRIEND_EX
{
public bool NewFriendPlaceHolder;
public int Uid;
public int Rid;
public int Level;
public long LastLoginTime;
public long UpdateTime;
public int ReincarnationCount;
public string Remarks = string.Empty;
}
public class SEND_INFO
{
public int Rid;
public long SendMailTime;
}
public class GROUP
{
public string Name = string.Empty;
public int GroupId;
public uint Color; // nếu dùng Unity thì có thể đổi sang Color
public List<Friend> Friends = new();
}
public class MESSAGE
{
public string SenderName = string.Empty;
public int SenderId;
public string MessageText = string.Empty;
public byte Flag;
}
// =========================
// Fields
// =========================
private List<GROUP> m_Groups = new();
private Dictionary<int, Friend> m_FriendTable = new();
private List<MESSAGE> m_OfflineMsgs = new();
private List<FRIEND_EX> m_FriendEx = new();
private List<SEND_INFO> m_SendInfo = new();
// =========================
// Operations
// =========================
public bool CheckInit() => m_Groups.Count > 0;
public Friend AddFriend(int id, int profession, int groupId, byte status, string name)
{
var friend = new Friend
{
Id = id,
Profession = profession,
GroupId = groupId,
Status = status,
Name = name
};
m_FriendTable[id] = friend;
var group = GetGroupByID(groupId);
group?.Friends.Add(friend);
if (friend.IsGameOnline())
{
// Todo: Check again
EC_Game.GetGameRun().AddPlayerName(id, name);
}
return friend;
}
public void RemoveFriend(int idFriend)
{
if (!m_FriendTable.TryGetValue(idFriend, out var friend))
return;
m_FriendTable.Remove(idFriend);
var group = GetGroupByID(friend.GroupId);
group?.Friends.Remove(friend);
}
public void RemoveAllFriends()
{
m_FriendTable.Clear();
foreach (var group in m_Groups)
group.Friends.Clear();
}
public void ChangeFriendStatus(int idFriend, byte status)
{
if (m_FriendTable.TryGetValue(idFriend, out var friend))
friend.Status = status;
}
public void ChangeFriendGroup(int idFriend, int newGroupId)
{
if (!m_FriendTable.TryGetValue(idFriend, out var friend))
return;
var oldGroup = GetGroupByID(friend.GroupId);
oldGroup?.Friends.Remove(friend);
friend.GroupId = newGroupId;
var newGroup = GetGroupByID(newGroupId);
newGroup?.Friends.Add(friend);
}
public Friend? GetFriendByID(int idFriend)
{
m_FriendTable.TryGetValue(idFriend, out var friend);
return friend;
}
public Friend? GetFriendByName(string name)
{
foreach (var friend in m_FriendTable.Values)
{
if (friend.Name == name)
return friend;
}
return null;
}
public void SetFriendLevel(int idFriend, int level)
{
if (m_FriendTable.TryGetValue(idFriend, out var friend))
friend.Level = level;
}
public void SetFriendArea(int idFriend, int areaId)
{
if (m_FriendTable.TryGetValue(idFriend, out var friend))
friend.AreaId = areaId;
}
// =========================
// Group Operations
// =========================
public bool AddGroup(int idGroup, string name)
{
if (GetGroupByID(idGroup) != null)
return false;
m_Groups.Add(new GROUP
{
GroupId = idGroup,
Name = name
});
return true;
}
public void RemoveGroup(int idGroup)
{
m_Groups.RemoveAll(g => g.GroupId == idGroup);
}
public void ChangeGroupName(int idGroup, string name)
{
var group = GetGroupByID(idGroup);
if (group != null)
group.Name = name;
}
public void SetGroupColor(int idGroup, uint color)
{
var group = GetGroupByID(idGroup);
if (group != null)
group.Color = color;
}
public int GetGroupNum() => m_Groups.Count;
public GROUP? GetGroupByIndex(int index)
{
if (index < 0 || index >= m_Groups.Count)
return null;
return m_Groups[index];
}
public GROUP? GetGroupByID(int id)
{
return m_Groups.Find(g => g.GroupId == id);
}
// =========================
// Offline Messages
// =========================
public int GetOfflineMsgNum() => m_OfflineMsgs.Count;
public MESSAGE? GetOfflineMsg(int index)
{
if (index < 0 || index >= m_OfflineMsgs.Count)
return null;
return m_OfflineMsgs[index];
}
public void RemoveAllOfflineMsgs()
{
m_OfflineMsgs.Clear();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b5aa739db9534099a9eead426912429c
timeCreated: 1772089238
@@ -1349,11 +1349,12 @@ namespace BrewMonster.Scripts
string message = $"Cannot find path to target position.\nPlease move manually to target location.\n\nMap Coordinates: ({mapX}, {mapZ}, ↑{mapY})";
string messageCN = $"无法找到到目标位置的路径。\n请手动移动到目标位置。\n\n地图坐标: ({mapX}, {mapZ}, ↑{mapY})";
CECUIManager.Instance.ShowMessageBox(
"Path Not Found", // 路径未找到
message, // English message with map coordinates
BrewMonster.MessageBoxType.YesButton
);
// CECUIManager.Instance.ShowMessageBox(
// "Path Not Found", // 路径未找到
// message, // English message with map coordinates
// BrewMonster.MessageBoxType.YesButton
// );
CECUIManager.Instance.ShowMessageBoxYes("Path Not Found", message, null, null);
}
else
{
@@ -11,7 +11,8 @@ namespace BrewMonster.Scripts.Managers
public class EC_Inventory
{
// ===== Instance-based inventory (per-pack) =====
public static int IVTRTYPE_CLIENT_GENERALCARD_PACK = 1024; // ͻ˱ذ ڿͼҪѻÿͨ͡ΪͳһڵƷӱذ
// Item array: index is slot, null means empty.
private EC_IvtrItem[] m_aItems = Array.Empty<EC_IvtrItem>();
@@ -27,12 +28,6 @@ namespace BrewMonster.Scripts.Managers
IVTRTYPE_GENERALCARD_BOX = 7; // ư
};
// ע IVTRTYPE_CLIENT_GENERALCARD_PACK öֵܺ Inventory type ֵظ
public static class IVTRTYPE_PACK_CLIENT_GENERALCAR
{
public const int IVTRTYPE_CLIENT_GENERALCARD_PACK = 1024; // ͻ˱ذ ڿͼҪѻÿͨ͡ΪͳһڵƷӱذ
};
public EC_Inventory()
{
}
@@ -201,8 +201,8 @@ namespace PerfectWorld.Scripts.Managers
StrengthReq = m_pDBEssence.require_strength;
AgilityReq = m_pDBEssence.require_agility;
ReputationReq = m_pDBEssence.require_reputation;
CurEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
CurEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
}
// Get item icon file name
public override string GetIconFile()
@@ -207,8 +207,8 @@ namespace PerfectWorld.Scripts.Managers
StrengthReq = m_pDBEssence.require_strength;
AgilityReq = m_pDBEssence.require_agility;
ReputationReq = m_pDBEssence.require_reputation;
CurEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
CurEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
}
// Get item icon file name
public override string GetIconFile()
@@ -174,8 +174,8 @@ namespace PerfectWorld.Scripts.Managers
StrengthReq = 0;
AgilityReq = 0;
ReputationReq = m_pDBEssence.require_reputation;
CurEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
CurEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
}
public override string GetIconFile()
{
@@ -14,7 +14,6 @@ using System.Text.RegularExpressions;
using System.Reflection;
using BrewMonster.Scripts.Managers;
using BrewMonster.Scripts;
namespace PerfectWorld.Scripts.Managers
{
/// <summary>
@@ -161,9 +160,6 @@ namespace PerfectWorld.Scripts.Managers
// Scale Types
public const int SCALE_SELL = 1;
// Endurance Scale
public const int ENDURANCE_SCALE = 100;
#endregion
#region Public Fields
@@ -925,7 +921,7 @@ namespace PerfectWorld.Scripts.Managers
/// </summary>
public static int VisualizeEndurance(int v)
{
return (v + ENDURANCE_SCALE - 1) / ENDURANCE_SCALE;
return (v + GameConstants.ENDURANCE_SCALE - 1) / GameConstants.ENDURANCE_SCALE;
}
/// <summary>
@@ -115,8 +115,8 @@ namespace BrewMonster.Scripts.Managers
magic_damage = dr.ReadInt();
defense = dr.ReadInt();
armor = dr.ReadInt();
resistance = new int[InventoryConst.NUM_MAGICCLASS];
for (int i = 0; i < InventoryConst.NUM_MAGICCLASS; i++)
resistance = new int[GameConstants.NUM_MAGICCLASS];
for (int i = 0; i < GameConstants.NUM_MAGICCLASS; i++)
{
resistance[i] = dr.ReadInt();
}
@@ -132,13 +132,13 @@ namespace BrewMonster.Scripts.Managers
public int[] resistance;
public IVTR_ESSENCE_ARMOR(byte[] data)
{
resistance = new int[InventoryConst.NUM_MAGICCLASS];
resistance = new int[GameConstants.NUM_MAGICCLASS];
CECDataReader dr = new(data, data.Length);
defense = dr.ReadInt();
armor = dr.ReadInt();
mp_enhance = dr.ReadInt();
hp_enhance = dr.ReadInt();
for (int i = 0; i < InventoryConst.NUM_MAGICCLASS; i++)
for (int i = 0; i < GameConstants.NUM_MAGICCLASS; i++)
{
resistance[i] = dr.ReadInt();
}
@@ -69,6 +69,7 @@ namespace PerfectWorld.Scripts.Managers
case EC_MsgDef.MSG_PM_PLAYERFLY:
case EC_MsgDef.MSG_PM_PLAYERMOUNT:
case EC_MsgDef.MSG_PM_PLAYERCHGSHAPE:
case EC_MsgDef.MSG_PM_PLAYERSKILLRESULT:
TransmitMessage(Msg);
break;
case EC_MsgDef.MSG_PM_PLAYERDIED:
@@ -141,14 +142,22 @@ namespace PerfectWorld.Scripts.Managers
if (host != null && cid == host.GetCharacterID())
{
host.ProcessMessage(Msg);
// Call OnMsgPlayerExtState directly instead of ProcessMessage
// ProcessMessage doesn't handle MSG_PM_PLAYEREXTSTATE
// 直接调用OnMsgPlayerExtState而不是ProcessMessage
// ProcessMessage不处理MSG_PM_PLAYEREXTSTATE
host.OnMsgPlayerExtState(Msg);
}
else
{
EC_ElsePlayer elsePlayer = SeekOutElsePlayer(cid);
if (elsePlayer != null)
{
elsePlayer.ProcessMessage(Msg);
// Call OnMsgPlayerExtState directly instead of ProcessMessage
// ProcessMessage doesn't handle MSG_PM_PLAYEREXTSTATE
// 直接调用OnMsgPlayerExtState而不是ProcessMessage
// ProcessMessage不处理MSG_PM_PLAYEREXTSTATE
elsePlayer.OnMsgPlayerExtState(Msg);
}
if (Convert.ToInt32(Msg.dwParam2) == CommandID.ICON_STATE_NOTIFY && host != null && host.GetTeam() != null)
@@ -735,6 +744,9 @@ namespace PerfectWorld.Scripts.Managers
case EC_MsgDef.MSG_PM_PLAYERCHGSHAPE:
cid = (GPDataTypeHelper.FromBytes<cmd_player_chgshape>((byte[])Msg.dwParam1)).idPlayer;
break;
case EC_MsgDef.MSG_PM_PLAYERSKILLRESULT:
cid = GPDataTypeHelper.FromBytes<cmd_object_skill_attack_result>((byte[])Msg.dwParam1).attacker_id;
break;
default:
System.Diagnostics.Debug.Assert(false, "Unknown message");
return false;
@@ -917,4 +929,4 @@ namespace PerfectWorld.Scripts.Managers
public CECModel pFlyNviagteModel;
//CECPlayer::EquipsLoadResult EquipResult;
};
}
}
@@ -13,9 +13,8 @@ using UnityEngine;
//
///////////////////////////////////////////////////////////////////////////
public class CECObject : MonoBehaviour
public partial class CECObject : MonoBehaviour
{
protected static int ALPHA_HASH = Shader.PropertyToID("_Alpha");
protected Quaternion targetRotation;
protected Quaternion startRotation; // Store starting rotation for Slerp
@@ -231,8 +231,7 @@ namespace BrewMonster.Scripts
{
// Constants
public const int EC_MAXNOPKLEVEL = 0; // The maximum no PK level
public const float EC_TABSEL_DIST = 60.0f; // Distance of TAB selection
public const int NUM_MAGICCLASS = 5;
public const float EC_TABSEL_DIST = 60.0f;
public const int NUM_ESBYTE = (int)(ExtendState.NUM_EXTSTATE + 31) >> 5;
// Helper methods for bit manipulation (equivalent to the C++ inline functions)
@@ -36,6 +36,11 @@ namespace BrewMonster
[SerializeField] internal INFO m_PlayerInfo;
public CECModel m_pPlayerCECModel;
protected GameObject m_pPlayerModel => m_pPlayerCECModel.m_pPlayerModel;
public bool IsPlayerModelReady
{
get;
protected set;
}
protected float rotationSpeed = 5;
internal int m_iMoveMode; // Player's move mode
[SerializeField] internal int m_idSelTarget;
@@ -331,6 +336,9 @@ namespace BrewMonster
m_pModels[(int)PLAYERMODEL_TYPE.PLAYERMODEL_MAJOR] = m_pPlayerModel;
m_iShape = 0;
// Set the player model ready flag
IsPlayerModelReady = true;
// Update visual components after model is set (with delay to ensure NamedAnimancerComponent is available)
// 设置模型后更新视觉组件(延迟以确保NamedAnimancerComponent可用)
StartCoroutine(UpdateVisualComponentsDelayed());
@@ -741,6 +749,15 @@ namespace BrewMonster
public virtual void SetNewExtendStates(int unknown, uint[] states, int count)
{
// TODO: Implement appearance or physics change
// Remove all trace target GFX when buff states change
// This ensures buff-related trail effects are cleaned up when buffs expire
// 当buff状态改变时移除所有跟踪目标GFX
// 这确保buff相关的轨迹效果在buff过期时被清理
if (SkillGfxMan.InstanceSub != null)
{
SkillGfxMan.InstanceSub.RemoveAllTraceTargetGfx();
}
}
public virtual void SetUpPlayer()
@@ -2202,7 +2219,45 @@ namespace BrewMonster
}
}
}
protected void OnMsgEnchantResult(ECMSG msg)
{
// 从消息中获取cmd_enchant_result结构 // Get cmd_enchant_result structure from message
cmd_enchant_result pCmd = GPDataTypeHelper.FromBytes<cmd_enchant_result>((byte[])msg.dwParam1);
// 初始化掩码为全1 // Initialize mask to all 1s
uint mask = 0xFFFFFFFF;
// 如果目标不是主机玩家且当前不是主机玩家,则过滤会引起气泡文本的修饰符
// We should filter out these things that will cause bubble texts
CECHostPlayer pHost = EC_ManMessageMono.Instance.GetECManPlayer.GetHostPlayer();
if (pCmd.target != pHost.GetCharacterID() && !IsHostPlayer())
{
mask &= (uint)(MOD.MOD_PHYSIC_ATTACK_RUNE | MOD.MOD_MAGIC_ATTACK_RUNE |
MOD.MOD_CRITICAL_STRIKE | MOD.MOD_ENCHANT_FAILED);
}
// 获取修饰符 // Get modifier
uint dwModifier = (uint)pCmd.attack_flag;
// 获取技能类型 // Get skill type
int nDamage = -2; // 默认为-2,不会引起受伤动作 // Default to -2, will not cause wounded action
if (ElementSkill.GetType((uint)pCmd.skill) == (byte)skill_type.TYPE_ATTACK)
{
// 只有攻击技能会引起受伤动作,伤害值为-1 // Only attack skill will cause wounded action, when damage is -1
nDamage = -1;
}
else
{
// 其他技能不会引起受伤动作,伤害值设为-2 // Other skills will not cause wounded action, so we set damage to -2
nDamage = -2;
}
// 播放攻击效果 // Play attack effect
int attackTime = 0;
PlayAttackEffect(pCmd.target, pCmd.skill, pCmd.level, nDamage,
dwModifier & mask, 0, ref attackTime, pCmd.section);
}
// 判断是否在飞行 / Check if flying
public bool IsFlying()
{
+30 -1
View File
@@ -6,6 +6,7 @@ using CSNetwork.GPDataType;
using ModelRenderer.Scripts.Common;
using System;
using System.Threading.Tasks;
using BrewMonster.Scripts.Chat;
using UnityEngine;
public class CECNPC : CECObject
@@ -469,7 +470,7 @@ public class CECNPC : CECObject
{
return m_pNPCModelPolicy.PlayAttackAction(nAttackSpeed, attackevent);
}
void NPCTurnFaceTo(int idTarget, float dwTime = 0.3f)
public void NPCTurnFaceTo(int idTarget, float dwTime = 0.3f)
{
if (IsDirFixed())
{
@@ -1602,6 +1603,34 @@ public class CECNPC : CECObject
// Get NPC name color
public virtual uint GetNameColor() { return 0xffffff00; }
CECPateText m_pPateLastWords1;
CECPateText m_pPateLastWords2;
public void SetLastSaidWords(string words, int timeShow = -1)
{
if (m_pPateLastWords1 == null)
{
m_pPateLastWords1 = new CECPateText();
}
if (m_pPateLastWords2 == null)
{
m_pPateLastWords2 = new CECPateText();
}
if (words == null)
return;
int len1 = m_pPateLastWords1.SetText(words, true, out string strWords, false, null);
/*
if (len1 < strWords.Length)
m_pPateLastWords2.SetText(strWords.Substring(len1), true, true);
else
m_pPateLastWords2.Clear();
m_iLastSayCnt = timeShow > 0 ? timeShow : 20000;*/
}
/// <summary>
/// Get NPC's CECModel instance
@@ -1,12 +1,34 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CSNetwork;
namespace CSNetwork
{
public static class AUICommon
{
public static void AUI_ConvertChatString(string pszChat, char[] pszConv, bool bName)
public const char AUICOMMON_ITEM_CODE_START = (char)0xE000;
public const char AUICOMMON_ITEM_CODE_END = (char)0xE3FF;
public const int AUIMANAGER_MAX_EMOTIONGROUPS = 32;
public const int MAX_EDITBOX_ITEM_NUM =
AUICOMMON_ITEM_CODE_END - AUICOMMON_ITEM_CODE_START + 1;
public const int MAXNUM_CUSTOM_ITEM = 255;
public enum EditboxItemType
{
enumEIEmotion = 0,
enumEIIvtrlItem,
enumEICoord,
enumEIImage,
enumEIScriptItem,
enumEICustom,
enumEINum = enumEICustom + MAXNUM_CUSTOM_ITEM
}
public static void AUI_ConvertChatString(ref string pszChat, ref char[] pszConv, bool bName)
{
if (string.IsNullOrEmpty(pszChat) || pszConv == null)
return;
@@ -49,5 +71,782 @@ namespace CSNetwork
pszConv[nLen] = '\0';
}
public static string FilterEmotionSet(string szText, int nEmotionSet)
{
EditBoxItemsSet itemsSet = new EditBoxItemsSet();
string strOrgText = UnmarshalEditBoxText(szText, itemsSet);
int nCount = itemsSet.GetItemCount();
if (nCount == 0)
return szText;
var it = itemsSet.GetItemIterator();
for (int i = 0; i < nCount; i++)
{
EditBoxItemBase pItem = it.Current.Value;
if (pItem.GetType() == EditboxItemType.enumEIEmotion)
{
int nSet = 0;
int nIndex = 0;
UnmarshalEmotionInfo(pItem.GetInfo(), ref nSet, ref nIndex);
pItem.SetInfo(MarshalEmotionInfo(nEmotionSet, nIndex));
}
it.MoveNext();
}
return MarshalEditBoxText(strOrgText, itemsSet);
}
public static string MarshalEmotionInfo(int nEmotionSet, int nIndex)
{
return nEmotionSet.ToString() + ":" + nIndex.ToString();
}
public static void UnmarshalEmotionInfo(string szText, ref int nEmotionSet, ref int nIndex)
{
if (string.IsNullOrEmpty(szText))
return;
var parts = szText.Split(':');
if (parts.Length >= 2)
{
int.TryParse(parts[0], out nEmotionSet);
int.TryParse(parts[1], out nIndex);
}
// a_ClampFloor(nEmotionSet, 0);
if (nEmotionSet < 0)
nEmotionSet = 0;
// a_ClampFloor(nIndex, 0);
if (nIndex < 0)
nIndex = 0;
if (nEmotionSet >= AUIMANAGER_MAX_EMOTIONGROUPS)
nEmotionSet = 0;
}
public static string MarshalEditBoxText(string text, EditBoxItemsSet itemsSet)
{
var sb = new StringBuilder(text.Length * 2);
int start = 0;
for (int i = 0; i < text.Length; i++)
{
char ch = text[i];
if (IsEditboxItemCode(ch))
{
sb.Append(text, start, i - start + 1);
var item = itemsSet.GetItemByChar(ch);
if (item != null)
sb.Append(item.Serialize());
start = i + 1;
}
}
if (start < text.Length)
sb.Append(text, start, text.Length - start);
return sb.ToString();
}
public static bool IsEditboxItemCode(char ch)
{
return ch >= AUICOMMON_ITEM_CODE_START && ch <= AUICOMMON_ITEM_CODE_END;
}
public static string UnmarshalEditBoxText(string szText, EditBoxItemsSet itemsSet)
{
return UnmarshalEditBoxText(
szText,
itemsSet,
0,
null,
0,
0,
null,
0,
false,
false,
0
);
}
public static string UnmarshalEditBoxText(
string? sztext,
EditBoxItemsSet itemsSet,
int msgIndex,
string ivtrItem,
uint clIvtrItem,
int itemMask,
EditboxScriptItem[]? scriptItems,
int scriptItemCount,
bool underLine,
bool sameColor,
uint clUnderLine)
{
if (sztext == null)
return "";
var scriptInfo = new AUI_UNMARSH_SCRIPTITEM_INFO
{
ScriptItems = scriptItems,
ScriptItemCount = scriptItemCount
};
var underlineInfo = new AUI_UNMARSH_UNDERLINE_INFO
{
UnderLine = underLine,
SameColor = sameColor,
UnderLineColor = clUnderLine
};
return UnmarshalEditBoxTextEx(
sztext,
itemsSet,
msgIndex,
ivtrItem,
clIvtrItem,
itemMask,
scriptInfo,
underlineInfo
);
}
public static string UnmarshalEditBoxTextEx(
string text,
EditBoxItemsSet itemsSet,
int msgIndex,
string ivtrItem,
uint clIvtrItem,
int itemMask,
AUI_UNMARSH_SCRIPTITEM_INFO? scriptInfo,
AUI_UNMARSH_UNDERLINE_INFO? underlineInfo)
{
if (text == null)
return "";
int start = 0;
int i = 0;
int curScriptIndex = 0;
var sb = new StringBuilder();
while (i < text.Length)
{
char ch = text[i];
if (IsEditboxItemCode(ch))
{
if (i > start)
sb.Append(text, start, i - start);
i++;
EditBoxItemBase? item = EditBoxItemBase.Unserialize(text, ref i);
start = i;
if (item != null)
{
if ((itemMask & (1 << (int)item.GetType())) != 0)
{
char newChar = itemsSet.AppendItem(item);
if (newChar != '\0')
{
sb.Append(newChar);
item.SetMsgIndex(msgIndex);
if (underlineInfo != null)
{
item.SetUnderLine(
underlineInfo.UnderLine,
underlineInfo.SameColor,
underlineInfo.UnderLineColor);
}
switch (item.GetType())
{
case EditboxItemType.enumEIIvtrlItem:
item.SetName(ivtrItem);
item.SetColor(clIvtrItem);
break;
case EditboxItemType.enumEIScriptItem:
if (scriptInfo != null &&
curScriptIndex < scriptInfo.ScriptItemCount)
{
var sItem = scriptInfo.ScriptItems![curScriptIndex];
item.SetName(sItem.Name);
item.SetColor(sItem.Color);
var data = sItem.Name;
if (data != null)
item.SetExtraData(sItem.Data, sItem.GetDataSize());
curScriptIndex++;
}
break;
}
}
}
}
}
else
{
i++;
}
}
if (i > start)
sb.Append(text, start, i - start);
return sb.ToString();
}
/// <summary>
/// Chuyển đổi định dạng printf (C-style: %s, %d) sang string.Format (C#-style: {0}, {1})
/// </summary>
public static string ConvertPrintfToCSharpFormat(string format)
{
if (string.IsNullOrEmpty(format)) return "";
StringBuilder sb = new StringBuilder();
int argIndex = 0;
for (int i = 0; i < format.Length; i++)
{
if (format[i] == '%' && i + 1 < format.Length)
{
char next = format[i + 1];
if (next == '%') // Trường hợp %% -> %
{
sb.Append('%');
i++;
}
else
{
sb.Append('{').Append(argIndex++).Append('}');
i++;
// Nhảy qua các ký tự định dạng (ví dụ: %02d, %ls, %f)
while (i < format.Length && (char.IsDigit(format[i]) || format[i] == '.' || format[i] == 'l' || format[i] == 'u' || format[i] == 'd' || format[i] == 's' || format[i] == 'f' || format[i] == 'x'))
{
// Nếu gặp ký tự kết thúc định dạng (s, d, f, ...) thì dừng lại sau ký tự đó
char c = format[i];
if (c == 's' || c == 'd' || c == 'f' || c == 'u' || c == 'x' || c == 'g')
{
// i++; // Đã ở đúng vị trí để vòng lặp cha thực hiện i++ tiếp theo
break;
}
i++;
}
}
}
else
{
sb.Append(format[i]);
}
}
return sb.ToString();
}
}
}
}
public class AUI_UNMARSH_SCRIPTITEM_INFO
{
public EditboxScriptItem[]? ScriptItems;
public int ScriptItemCount;
}
public class AUI_UNMARSH_UNDERLINE_INFO
{
public bool UnderLine;
public bool SameColor;
public uint UnderLineColor;
}
public class EditBoxItemsSet
{
const char AUICOMMON_ITEM_CODE_START = '\u0001';
const char AUICOMMON_ITEM_CODE_END = '\u0010';
protected Dictionary<char, EditBoxItemBase> m_Items = new();
protected int[] m_ItemsCount = new int[(int)AUICommon.EditboxItemType.enumEINum];
protected char m_cNextItemChar;
public EditBoxItemsSet()
{
Array.Clear(m_ItemsCount, 0, m_ItemsCount.Length);
m_cNextItemChar = AUICommon.AUICOMMON_ITEM_CODE_START;
}
public EditBoxItemsSet(EditBoxItemsSet itemsset)
{
this.Assign(itemsset);
}
public void Assign(EditBoxItemsSet src)
{
m_Items.Clear();
foreach (var kv in src.m_Items)
{
m_Items[kv.Key] = new EditBoxItemBase(kv.Value);
}
Array.Copy(src.m_ItemsCount, m_ItemsCount, m_ItemsCount.Length);
m_cNextItemChar = src.m_cNextItemChar;
}
public void Release()
{
m_Items.Clear();
Array.Clear(m_ItemsCount, 0, m_ItemsCount.Length);
m_cNextItemChar = AUICommon.AUICOMMON_ITEM_CODE_START;
}
public int GetItemCount()
{
return m_Items.Count;
}
public Dictionary<char, EditBoxItemBase>.Enumerator GetItemIterator()
{
return m_Items.GetEnumerator();
}
public EditBoxItemBase? GetItemByChar(char ch)
{
if (!IsEditboxItemCode(ch))
throw new Exception("Invalid editbox item char");
if (m_Items.TryGetValue(ch, out var item))
return item;
return null;
}
public bool IsEditboxItemCode(char ch)
{
return ch >= AUICOMMON_ITEM_CODE_START && ch <= AUICOMMON_ITEM_CODE_END;
}
public int GetItemCountByType(AUICommon.EditboxItemType type)
{
return m_ItemsCount[(int)type];
}
public void DelItemByChar(char ch)
{
if (m_Items.TryGetValue(ch, out var item))
{
m_ItemsCount[(int)item.GetType()]--;
m_Items.Remove(ch);
}
}
public char AppendItem(EditBoxItemBase pItem)
{
if (m_Items.Count >= AUICommon.MAX_EDITBOX_ITEM_NUM)
return '\0';
char cur = m_cNextItemChar;
do
{
if (m_Items.ContainsKey(m_cNextItemChar))
{
m_cNextItemChar = EditboxGetNextChar(m_cNextItemChar);
}
else
{
m_Items[m_cNextItemChar] = pItem;
m_ItemsCount[(int)pItem.GetType()]++;
char ret = m_cNextItemChar;
m_cNextItemChar = EditboxGetNextChar(m_cNextItemChar);
return ret;
}
} while (m_cNextItemChar != cur);
return '\0';
}
public char EditboxGetNextChar(char cur)
{
if (cur >= AUICOMMON_ITEM_CODE_END)
return AUICOMMON_ITEM_CODE_START;
else
return (char)(cur + 1);
}
public char AppendItem(AUICommon.EditboxItemType type, uint cl, string szName, string szInfo)
{
// Implement theo logic C++ gốc
throw new NotImplementedException();
}
public int GetTotalExtraDataSize()
{
int sz = 0;
foreach (var item in m_Items.Values)
{
sz += item.GetExtraDataSize();
}
return sz;
}
}
public class EditBoxItemBase
{
protected AUICommon.EditboxItemType m_type;
protected uint m_dwColor;
protected string m_strName = "";
protected string m_strInfo = "";
protected int m_nMsgIndex;
protected int m_nImageIndex;
protected int m_nImageFrame;
protected float m_fImageScale;
protected byte[]? m_pExtraData;
protected int m_uExtraDataSize;
protected bool m_bUnderLine;
protected bool m_bSameColor;
protected uint m_dwUnderLineColor;
protected static EditBoxItemBase?[] m_mapCustomType = new EditBoxItemBase[AUICommon.MAXNUM_CUSTOM_ITEM];
public EditBoxItemBase(AUICommon.EditboxItemType type)
{
m_type = type;
m_dwColor = 0xffffffff;
m_nMsgIndex = 0;
m_nImageIndex = 0;
m_nImageFrame = 0;
m_fImageScale = 1.0f;
m_bUnderLine = false;
m_bSameColor = true;
m_dwUnderLineColor = 0;
RegisterCustomType(type);
}
public EditBoxItemBase(EditBoxItemBase src)
{
Assign(src);
}
public bool UnserializeContent(string text, ref int index)
{
int szNext;
int szEnd = text.IndexOf("<", index);
if (szEnd == -1)
return false;
if (m_type == AUICommon.EditboxItemType.enumEICoord)
{
szNext = szEnd + 1;
if (!TryParseInt(text, szNext, out int color))
return false;
SetColor((uint)color);
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szNext = szEnd + 2;
if (!TryParseInt(text, szNext, out int underline))
return false;
m_bUnderLine = underline != 0;
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szNext = szEnd + 2;
if (!TryParseInt(text, szNext, out int underlineColor))
return false;
m_dwUnderLineColor = (uint)underlineColor;
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
m_bSameColor = (m_dwUnderLineColor == m_dwColor);
szNext = szEnd + 2;
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
SetName(text.Substring(szNext, szEnd - szNext));
szEnd += 1;
}
else if (m_type == AUICommon.EditboxItemType.enumEIImage)
{
szNext = szEnd + 1;
if (!TryParseUInt(text, szNext, out uint color))
return false;
SetColor(color);
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szNext = szEnd + 2;
if (!TryParseInt(text, szNext, out int imageIndex))
return false;
SetImageIndex(imageIndex);
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szNext = szEnd + 2;
if (!TryParseInt(text, szNext, out int frame))
return false;
SetImageFrame(frame);
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szNext = szEnd + 2;
if (!float.TryParse(text.Substring(szNext), out float f))
return false;
SetImageScale(f);
szEnd = text.IndexOf("><", szNext);
if (szEnd == -1) return false;
szEnd += 1;
}
else if (m_type == AUICommon.EditboxItemType.enumEIEmotion)
{
SetName("W");
}
szNext = szEnd + 1;
szEnd = text.IndexOf('>', szNext);
if (szEnd == -1)
return false;
SetInfo(text.Substring(szNext, szEnd - szNext));
index = szEnd + 1;
return true;
}
private bool TryParseInt(string text, int start, out int value)
{
int end = start;
while (end < text.Length && char.IsDigit(text[end]))
end++;
return int.TryParse(text.Substring(start, end - start), out value);
}
private bool TryParseUInt(string text, int start, out uint value)
{
int end = start;
while (end < text.Length && char.IsDigit(text[end]))
end++;
return uint.TryParse(text.Substring(start, end - start), out value);
}
protected void RegisterCustomType(AUICommon.EditboxItemType type)
{
if (type >= AUICommon.EditboxItemType.enumEICustom &&
type < AUICommon.EditboxItemType.enumEINum)
{
int index = (int)type - (int)AUICommon.EditboxItemType.enumEICustom;
if (m_mapCustomType[index] == null)
m_mapCustomType[index] = this;
}
}
protected virtual EditBoxItemBase Create()
{
return new EditBoxItemBase(m_type);
}
protected static EditBoxItemBase? GetCustomItemFromType(AUICommon.EditboxItemType type)
{
if (type >= AUICommon.EditboxItemType.enumEICustom &&
type < AUICommon.EditboxItemType.enumEINum)
{
return m_mapCustomType[(int)type - (int)AUICommon.EditboxItemType.enumEICustom];
}
return null;
}
public uint GetColor() => m_dwColor;
public AUICommon.EditboxItemType GetType() => m_type;
public string GetName() => m_strName;
public string GetInfo() => m_strInfo;
public int GetMsgIndex() => m_nMsgIndex;
public int GetImageIndex() => m_nImageIndex;
public int GetImageFrame() => m_nImageFrame;
public float GetImageScale() => m_fImageScale;
public bool GetUnderLine() => m_bUnderLine;
public bool GetSameColor() => m_bSameColor;
public uint GetUnderLineColor() => m_dwUnderLineColor;
public void SetColor(uint cl) => m_dwColor = cl;
public void SetName(string name) => m_strName = name;
public void SetInfo(string info) => m_strInfo = info;
public void SetMsgIndex(int n) => m_nMsgIndex = n;
public void SetImageIndex(int n) => m_nImageIndex = n;
public void SetImageFrame(int n) => m_nImageFrame = n;
public void SetImageScale(float f) => m_fImageScale = f;
public byte[]? GetExtraData() => m_pExtraData;
public int GetExtraDataSize() => m_uExtraDataSize;
public void SetUnderLine(bool underline, bool sameColor = true, uint underlineColor = 0)
{
m_bUnderLine = underline;
m_bSameColor = sameColor;
m_dwUnderLineColor = underlineColor;
}
public void SetExtraData(byte[] data, int size)
{
m_pExtraData = new byte[size];
Array.Copy(data, 0, m_pExtraData, 0, size);
m_uExtraDataSize = size;
}
public virtual string Serialize()
{
return "";
}
public static EditBoxItemBase Unserialize(string text, ref int index)
{
int start = text.IndexOf('<', index);
if (start == -1)
return null;
start++;
int endType = text.IndexOf('>', start);
if (endType == -1)
return null;
string typeStr = text.Substring(start, endType - start);
if (!int.TryParse(typeStr, out int type))
return null;
if (type < 0 || type >= (int)AUICommon.EditboxItemType.enumEINum)
return null;
index = endType + 1;
EditBoxItemBase pItem = EditBoxItemBase.GetCustomItemFromType((AUICommon.EditboxItemType)type);
EditBoxItemBase pItemNew;
if (pItem == null)
{
pItemNew = new EditBoxItemBase((AUICommon.EditboxItemType)type);
}
else
{
pItemNew = pItem.Create();
}
if (!pItemNew.UnserializeContent(text, ref index))
{
return null;
}
return pItemNew;
}
public void Assign(EditBoxItemBase src)
{
m_type = src.m_type;
m_dwColor = src.m_dwColor;
m_strName = src.m_strName;
m_strInfo = src.m_strInfo;
m_nMsgIndex = src.m_nMsgIndex;
m_nImageIndex = src.m_nImageIndex;
m_nImageFrame = src.m_nImageFrame;
m_fImageScale = src.m_fImageScale;
if (src.m_pExtraData != null)
{
SetExtraData(src.m_pExtraData, src.m_uExtraDataSize);
}
else
{
m_pExtraData = null;
m_uExtraDataSize = 0;
}
RegisterCustomType(m_type);
}
}
public class EditboxScriptItem
{
public string Name { get; set; } = "";
public uint Color { get; set; }
public byte[]? Data { get; private set; }
public void SetData(byte[] data)
{
Data = data.ToArray(); // deep copy
}
public int GetDataSize()
{
return Data?.Length ?? 0;
}
}
@@ -381,7 +381,7 @@ namespace CSNetwork.C2SCommand
public static Octets CreateGetOtherEquipCmd(int iNumID, int[] aIDs)
{
var cmd = new CMD_GetOtherEquip { size = (ushort)iNumID, idList = aIDs };
return SerializeCommand(CommandID.GET_OTHER_EQUIP, cmd);
return SerializeCommand(CommandID.GET_OTHER_EQUIP, cmd, false);
}
/// <summary>Create C2S GET_OTHER_EQUIP_DETAIL command (view other player profile/equip). Sends cmd + roleId only (4-byte body).</summary>
@@ -1539,6 +1539,18 @@ namespace CSNetwork.GPDataType
public byte section;
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct cmd_host_skill_attacked
{
public int idAttacker;
public int idSkill;
public int iDamage;
public byte cEquipment; // The equipment which is mangled, λιDzӦñɫ
public int attack_flag; //ǸùǷйŻͷŻػ
public byte speed; //ٶ speed * 50 ms
public byte section; // skill section
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct cmd_matter_info_list
{
public ushort count;
@@ -1800,6 +1812,46 @@ namespace CSNetwork.GPDataType
Marshal.FreeHGlobal(ptr);
}
}
/// <summary>
/// Convert data become byte array (Demo)
/// </summary>
/// <param name="dataTypeLenght">Marshal.SizeOf<Data Type>()</param>
/// <param name="totalSize">Max size data</param>
/// <param name="lenghtHeaderIgnore">Removes the number of leading bytes</param>
/// <param name="lenghtLastIgnore">Remove the last byte count</param>
/// <returns></returns>
public static byte[] ContentBytes(int dataTypeLenght, int totalSize, int lenghtHeaderIgnore, int lenghtLastIgnore = 0)
{
int dataLength = totalSize - (lenghtHeaderIgnore + lenghtLastIgnore);
if(dataLength <= 0) throw new ArgumentException("Buffer không đủ dữ liệu");
byte[] dataContent = new byte[dataLength];
int offset = dataTypeLenght;
for (int i = 0; i < dataLength; i++)
{
dataContent[i] = FromBytes<byte>(dataContent, offset);
offset += dataTypeLenght;
}
return dataContent;
}
public static byte[] ContentBytes<T>(int totalSize, int lenghtHeaderIgnore, int lenghtLastIgnore = 0)
{
int dataLength = totalSize - (lenghtHeaderIgnore + lenghtLastIgnore);
if(dataLength <= 0) throw new ArgumentException("Buffer không đủ dữ liệu");
byte[] dataContent = new byte[dataLength];
int dataTypeSize = Marshal.SizeOf<T>();
int offset = dataTypeSize;
for (int i = 0; i < dataLength; i++)
{
dataContent[i] = FromBytes<byte>(dataContent, offset);
offset += dataTypeSize;
}
return dataContent;
}
/// <summary>Parse variable-length cmd_team_member_data from buffer. Returns (header, data[]) and bytes consumed.</summary>
public static (cmd_team_member_data_header header, cmd_team_member_data_MEMBER[] data) ParseTeamMemberData(byte[] data, int startIndex = 0)
{
@@ -14,9 +14,15 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BrewMonster.PerfectWorld.Scripts.UI;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Chat;
using BrewMonster.Scripts.Managers;
using BrewMonster.Scripts.UI;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Managers;
using UnityEngine;
using CECPlayer = BrewMonster.CECPlayer;
using CommandID = CSNetwork.GPDataType.CommandID;
namespace CSNetwork
@@ -51,6 +57,15 @@ namespace CSNetwork
/// <summary>Raised when server sends PROTOCOL_PLAYERLOGOUT(69).</summary>
public event Action<playerlogout> PlayerLogoutReceived;
/// <summary>
/// Raised when server sends RPC_ADDFRIENDRQST(204) — another player wants to add you as friend.
/// Args: xid (RPC transaction id), srcroleid, askerName.
/// </summary>
public event Action<uint, int, string> FriendRequestReceived;
/// <summary>Raised when server sends PROTOCOL_ADDFRIEND_RE(203). Args: retcode (0=success), message.</summary>
public event Action<byte, string> AddFriendResultReceived;
/// <summary>Raised when the underlying network disconnects.</summary>
public event Action Disconnected;
@@ -253,7 +268,7 @@ namespace CSNetwork
Debug.Log($"[GameSession] Creating role - UserID: {_currentUserId}, Localsid: {_localsid}, Profession: {roleInfo.occupation}, Gender: {roleInfo.gender}");
Debug.Log($"[GameSession] RoleInfo details - Name size: {roleInfo.name?.Size ?? 0}, Equipment count: {roleInfo.equipment?.Count ?? 0}, Custom data size: {roleInfo.custom_data?.Size ?? 0}, Race: {roleInfo.race}");
// Log first few bytes of custom_data for debugging
if (roleInfo.custom_data != null && roleInfo.custom_data.Size > 0)
{
@@ -262,7 +277,7 @@ namespace CSNetwork
string hexPreview = BitConverter.ToString(customDataPreview).Replace("-", " ");
Debug.Log($"[GameSession] Custom_data preview (first 16 bytes): {hexPreview}");
}
Debug.Log($"[GameSession] Sending createrole protocol (Type: {createRoleProtocol.Type})");
SendProtocol(createRoleProtocol);
}
@@ -325,7 +340,7 @@ namespace CSNetwork
// Initialize custom data exactly as C++ does: memset to 0, then set specific values
// This matches C++ LoadDefaultCustomizeData behavior
roleInfo.custom_data = CreateDefaultCustomizeData(profession, gender);
// Initialize other empty custom data fields
roleInfo.custom_status = new Octets();
roleInfo.charactermode = new Octets();
@@ -364,31 +379,31 @@ namespace CSNetwork
const int CUSTOMIZE_DATA_SIZE = 176; // Match server expectation (176 bytes from response)
const uint CUSTOMIZE_DATA_VERSION = 0x10007001; // CUSTOMIZE_DATA_VERSION from C++
byte[] customDataBytes = new byte[CUSTOMIZE_DATA_SIZE];
// Step 1: memset to 0 (already done by new byte[])
// Step 2: Set dwVersion at offset 0-3 (little-endian)
byte[] versionBytes = BitConverter.GetBytes(CUSTOMIZE_DATA_VERSION);
Array.Copy(versionBytes, 0, customDataBytes, 0, 4);
// Step 3: FACE_CUSTOMIZEDATA at offset 4-87 is already zero (84 bytes)
// (In C++ this would be loaded from INI, but for now we use zeros)
// Step 4: bodyID at offset 88-89 is already zero (2 bytes)
// Note: There might be 2 bytes padding here if struct is 4-byte aligned
// Step 5: Set colorBody to 0xffffffff
// Step 5: Set colorBody to 0xffffffff
// Try offset 90 first (no padding), if that doesn't work try 92 (with padding)
byte[] colorBodyBytes = BitConverter.GetBytes(0xffffffffu);
Array.Copy(colorBodyBytes, 0, customDataBytes, 90, 4); // Try 90 first
// Step 6: Set all 6 scale fields to 128
// Try offset 94 first (no padding), if that doesn't work try 96 (with padding)
for (int i = 94; i < 100; i++)
{
customDataBytes[i] = 128;
}
return new Octets(customDataBytes);
}
@@ -531,7 +546,6 @@ namespace CSNetwork
private void OnProtocolReceived(Protocol protocol)
{
_logger.Log(LogType.Debug, $"Received protocol: {protocol.GetType().Name} (Type: {protocol.Type})");
BMLogger.Log($"Received protocol: {protocol.GetType().Name} (Type: {protocol.Type})");
if (protocol is null)
return;
@@ -569,6 +583,9 @@ namespace CSNetwork
case ProtocolType.PROTOCOL_CHATMESSAGE:
OnPrtcChatMessage(protocol, false);
break;
case ProtocolType.PROTOCOL_WORLDCHAT:
OnPrtcWorldChat(protocol, false);
break;
case ProtocolType.PROTOCOL_PLAYERBASEINFO_RE:
OnPrtcPlayerBaseInfoRe(protocol);
break;
@@ -587,6 +604,14 @@ namespace CSNetwork
}
break;
case ProtocolType.RPC_ADDFRIENDRQST:
OnAddFriendRqst((addfriendrqst)protocol);
break;
case ProtocolType.PROTOCOL_ADDFRIEND_RE:
OnAddFriendRe((addfriend_re)protocol);
break;
default:
_logger.Log(LogType.Warning, $"Received unhandled protocol type: {protocol.GetPType()}");
break;
@@ -601,6 +626,49 @@ namespace CSNetwork
// PostToUnityContext(() => PlayerLogoutReceived?.Invoke(protocol));
}
private void OnAddFriendRqst(addfriendrqst p)
{
string askerName = "";
if (p.Srcname != null && p.Srcname.Size > 0)
askerName = System.Text.Encoding.Unicode.GetString(p.Srcname.ToArray(), 0, p.Srcname.Size);
// Forward xid + srcroleid + name to Unity layer so it can answer via Friend_AddResponse(xid, agree)
PostToUnityContext(() => FriendRequestReceived?.Invoke(p.Xid, p.Srcroleid, askerName));
}
private void OnAddFriendRe(addfriend_re p)
{
string friendName = "";
if (p.info?.name != null && p.info.name.Size > 0)
friendName = System.Text.Encoding.Unicode.GetString(p.info.name.ToArray(), 0, p.info.name.Size);
string msg = p.retcode == 0
? $"Add friend success: {friendName}"
: $"Add friend failed: {GetAddFriendRetcodeMessage(p.retcode)}";
if (p.retcode == 0)
{
Friend_GetList();
}
PostToUnityContext(() => AddFriendResultReceived?.Invoke(p.retcode, msg));
}
/// <summary>FS_ERR server retcodes for addfriend_re.</summary>
private static string GetAddFriendRetcodeMessage(byte retcode)
{
switch (retcode)
{
case 0: return "Success";
case 1: return "Player is offline";
case 2: return "Request was refused";
case 3: return "Timeout";
case 4: return "No remaining space";
case 5: return "Not found";
case 6: return "Invalid parameter";
case 7: return "Duplicate entry";
case 8: return "Friend list has not been retrieved yet";
default: return $"retcode={retcode}";
}
}
// void CECGameSession::OnPrtcPlayerLogout(GNET::Protocol* pProtocol)
// {
// using namespace GNET;
@@ -989,7 +1057,7 @@ namespace CSNetwork
}
else if (pCmd.iMessage == 133 || pCmd.iMessage == 134)
{
// deal failed
// deal failed
//pGameRun.PostMessage(MSG_HST_BUY_SELL_FAIL, MAN_PLAYER, 0, (DWORD)pDataBuf, pCmdHeader.cmd);
}
else if (pCmd.iMessage == 158)
@@ -1115,6 +1183,9 @@ namespace CSNetwork
case CommandID.HOST_SKILL_ATTACK_RESULT:
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader);
break;
case CommandID.HOST_SKILL_ATTACKED:
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLATTACKED, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader);
break;
case CommandID.CHANGE_FACE_START:
case CommandID.CHANGE_FACE_END:
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CHANGEFACE, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize);
@@ -1300,6 +1371,16 @@ namespace CSNetwork
case CommandID.LEAVE_SANCTUARY:
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SANCTUARY, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader);
break;
case CommandID.OBJECT_SKILL_ATTACK_RESULT:
{
cmd_object_skill_attack_result pCmd = GPDataTypeHelper.FromBytes <cmd_object_skill_attack_result>((byte[])pDataBuf);
if (ISPLAYERID(pCmd.attacker_id))
EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERSKILLRESULT, MANAGER_INDEX.MAN_PLAYER, -1,pDataBuf, pCmdHeader);
else if (ISNPCID(pCmd.attacker_id))
EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCSKILLRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader);
break;
}
default:
#if UNITY_EDITOR
if (isDebug)
@@ -1326,7 +1407,7 @@ namespace CSNetwork
private void HandleCreateRoleResponse(createrole_re protocol)
{
Debug.Log($"[GameSession] HandleCreateRoleResponse - result: {protocol.result}, roleid: {protocol.roleid}");
if (protocol.result != (int)ErrCode.ERR_SUCCESS)
{
string errorMsg = $"Create role failed with result code: {protocol.result} (ERR_SUCCESS = {(int)ErrCode.ERR_SUCCESS})";
@@ -1347,7 +1428,7 @@ namespace CSNetwork
private void HandleErrorInfo(errorinfo protocol)
{
Debug.LogError($"[GameSession] Server error - Errcode: {protocol.Errcode}");
// If we're waiting for create role response and get an error, fail the callback
if (_createRoleCallback != null)
{
@@ -1639,8 +1720,8 @@ namespace CSNetwork
g.Data = C2SCommandFactory.CreatePlayerLogoutCmd(outType);
SendProtocol(g, complete);
}
public void c2s_SendCmdStopMove(in Vector3 vDest, float fSpeed, int iMoveMode,
byte byDir, ushort wStamp, int iTime)
{
@@ -1657,17 +1738,94 @@ namespace CSNetwork
gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.ENTER_PK_PROTECTED);
SendProtocol(gamedatasend);
}
public const int SUPER_FAR_CRY_EMOTION_SET = 6;
/// <summary>
/// Convert string become byte (Encoding.Unicode trong .NET = UTF-16 LE)
/// </summary>
/// <param name="cChannel"> Data type: enum, Chanel chat </param>
/// <param name="szMsg"> Text send to server </param>
/// <param name="iPack"></param>
/// <param name="iSlot"></param>
public void SendChatData(byte cChannel, in string szMsg, int iPack, int iSlot)
{
publicchat publicChat = new publicchat();
publicChat.Channel = cChannel;
publicChat.Roleid = m_iCharID;
if (string.IsNullOrEmpty(szMsg))
return;
publicchat p = new publicchat();
p.Channel = cChannel;
p.Roleid = m_iCharID;
if (iPack == EC_Inventory.IVTRTYPE_CLIENT_GENERALCARD_PACK)
{
CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer();
EC_Inventory clientPack =
pHost != null ? pHost.GetPack(EC_Inventory.IVTRTYPE_CLIENT_GENERALCARD_PACK) : null;
if (pHost != null && clientPack != null)
{
EC_IvtrItem pCard = clientPack.GetItem(iSlot);
if (pCard != null)
{
short cmd = (short)CHAT_S2C.EChatS2CCommand.CHAT_GENERALCARD_COLLECTION;
int cardId = pCard.GetTemplateID();
byte[] bytes = new byte[6];
BitConverter.GetBytes(cmd).CopyTo(bytes, 0);
BitConverter.GetBytes(cardId).CopyTo(bytes, 2);
p.Data.Replace(bytes);
}
}
}
else if (iPack >= 0 && iSlot >= 0)
{
byte[] bytes = new byte[5];
short cmd = (short)CHAT_S2C.EChatS2CCommand.CHAT_EQUIP_ITEM;
short index = (short)iSlot;
byte where = (byte)iPack;
BitConverter.GetBytes(cmd).CopyTo(bytes, 0);
bytes[2] = where;
BitConverter.GetBytes(index).CopyTo(bytes, 3);
p.Data.Replace(bytes);
}
byte[] unicodeBytes = Encoding.Unicode.GetBytes(szMsg);
publicChat.Msg.Replace(unicodeBytes);
_logger.Log(LogType.Warning, $"HoangDev : publicChat {publicChat}");
SendProtocol(publicChat);
p.Msg.Replace(unicodeBytes);
SendProtocol(p);
if (cChannel is (byte)ChatChannel.GP_CHAT_LOCAL
or (byte)ChatChannel.GP_CHAT_FARCRY
or (byte)ChatChannel.GP_CHAT_SUPERFARCRY
or (byte)ChatChannel.GP_CHAT_BATTLE
or (byte)ChatChannel.GP_CHAT_COUNTRY) {
CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer();
//int nEmotionSet = pHost.GetCurEmotionSet();
int nEmotionSet = 0;
string strMsg = szMsg;
if (cChannel == (byte)ChatChannel.GP_CHAT_SUPERFARCRY)
{
if (strMsg.Length > 8)
{
strMsg = strMsg.Substring(0, strMsg.Length - 8);
}
nEmotionSet = SUPER_FAR_CRY_EMOTION_SET;
}
EC_IvtrItem pItem = null;
if (iPack >= 0 && iSlot >= 0) {
EC_Inventory pPack = pHost.GetPack(iPack);
if (pPack != null) {
pItem = pPack.GetItem(iSlot);
}
}
EventBus.PublishChannel(pHost.GetCharacterID(), new EventChatMessageOnTopPlayer(pHost.GetCharacterID(), szMsg));
EventBus.Publish(new ChatMessageEvent(szMsg, p.Channel));
}
}
public void LoadConfigData()
{
getuiconfig p = new getuiconfig();
@@ -1682,7 +1840,7 @@ namespace CSNetwork
public void SaveConfigData(byte[] pBuf, int len)
{
BMLogger.Log($"[MH] Session.SaveConfigData | len={len}");
if (pBuf == null || len <= 0) return;
var p = new setuiconfig();
p.Roleid = m_iCharID;
@@ -1690,7 +1848,7 @@ namespace CSNetwork
byte[] slice = new byte[len];
Buffer.BlockCopy(pBuf, 0, slice, 0, len);
p.Ui_config = new Octets(slice);
// return;
SendProtocol(p);
}
@@ -1700,23 +1858,226 @@ namespace CSNetwork
m_iCharID = iCharID;
}
private void OnPrtcChatMessage(Protocol pProtocol, bool bCalledagain)
private bool OnPrtcWorldChat(Protocol pProtocol, bool bCalledagain)
{
worldchat p = (worldchat)pProtocol;
/*if (p.Channel == (byte)ChatChannel.GP_CHAT_FARCRY && p.Roleid == 24)
{
OnTaskChatMessage(p.Msg.RawBuffer, p.Msg.Size);
return true;
}*/
// TODO: Porting logic OnPrtcWorldChat hoàn chỉnh từ C++ if needed
Debug.Log("[Cuong] OnPrtcWorldChat");
return true;
}
/*private void OnTaskChatMessage(byte[] pBuf, int sz)
{
... logic n ...
}*/
private bool OnBattleChatMessage(chatmessage p, List<int> pPendingFactions)
{
// Tương đương OnBattleChatMessage trong C++
// Hiện tại đơn giản hóa việc hiển thị, thực tế cần parse nội dung Battle cụ thể
Debug.Log($"[Battle Chat] RoleID: {p.Srcroleid}");
return true;
}
private bool OnFortressChatMessage(chatmessage p, List<int> pPendingFactions)
{
// Tương đương OnFortressChatMessage trong C++
Debug.Log($"[Fortress Chat] RoleID: {p.Srcroleid}");
return true;
}
private bool OnKingChatMessage(chatmessage p, bool bGetPlayerName = true)
{
// Tương đương OnKingChatMessage trong C++
Debug.Log($"[King Chat] RoleID: {p.Srcroleid}");
return true;
}
private void OnFactionPVPChatMessage(chatmessage p)
{
// Tương đương OnFactionPVPChatMessage trong C++
Debug.Log($"[FactionPVP Chat] RoleID: {p.Srcroleid}");
}
private bool OnPrtcChatMessage(Protocol pProtocol, bool bCalledagain)
{
CECGameUIMan pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan();
chatmessage p = (chatmessage)pProtocol;
//var channel = (ChatChannel)p.Channel;
Debug.Log("[Cuong] reciver chat channel: " + p.Channel);
string strTemp = System.Text.Encoding.Unicode.GetString(p.Msg.ToArray(), 0, p.Msg.Length);
if (Chat_GameSession.ShouldBlockByLevel(p))
{
Debug.Log("[Cuong] 1");
return true;
}
_logger.Log(LogType.Warning, $"HoangDev : OnPrtcChatMessage :{strTemp}");
EventBus.Publish(new ChatMessageEvent(strTemp));
EC_IvtrItem pItem = CHAT_S2C.CreateChatItem(p.Data);
string szMsg = null;
string strTemp = Encoding.Unicode.GetString(p.Msg.ToArray(), 0, p.Msg.Length);
string strMsgOrigion = strTemp;
// Todo: Show Text on Ui Game
//strTemp = pGameUI.FilterInvalidTags(strTemp, pItem==NULL);
/*if (!Chat_GameSession.PolicyResolver(pProtocol, p, ref strTemp))
{
Debug.Log("[Cuong] 2");
return false;
}
if (Chat_GameSession.HanldeGPChatSystem(p, bCalledagain))
{
Debug.Log("[Cuong] 3");
}*/
if (p.Channel is (byte)ChatChannel.GP_CHAT_BROADCAST
or (byte)ChatChannel.GP_CHAT_SYSTEM || p.Srcroleid == 0)
{
if (p.Channel == (byte)ChatChannel.GP_CHAT_SYSTEM && p.Srcroleid > 0)
{
switch (p.Srcroleid)
{
case 1: case 2: case 3: case 4: case 6: case 7: // Battle Message
if (!bCalledagain)
{
List<int> pending = new List<int>();
if (!OnBattleChatMessage(p, pending))
{
// Handle pending faction info
return false;
}
}
break;
case 18: case 19: case 20: case 21: case 22: // Auction Message
// pGameUI.AddSysAuctionMessage(...)
Debug.Log("[Auction] " + strTemp);
EventBus.Publish(new ChatMessageEvent(strTemp, p.Channel));
break;
case 24: // Task Message
// OnTaskChatMessage(p.Data.RawBuffer, p.Data.Size);
break;
case 29: case 30: case 31: case 32: case 33: case 34:
case 35: case 36: case 37: case 38: case 39: case 40:
case 41: case 42: case 43: case 45: // Fortress Message
OnFortressChatMessage(p, null);
break;
case 46: case 47: case 48: case 49: // Country Battle
// OnCountryChatMessage(p);
break;
case 50: case 51: case 52: case 53: case 54:
case 55: case 56: case 57: case 58: case 59: // King Chat
OnKingChatMessage(p);
break;
case 60: case 61: case 62: case 63: case 64: // Faction PVP
OnFactionPVPChatMessage(p);
break;
}
}
else
{
Debug.Log("[Cuong] 5");
EventBus.Publish(new ChatMessageEvent(strTemp, p.Channel));
}
}else if (p.Channel == (byte)ChatChannel.GP_CHAT_INSTANCE && p.Srcroleid == 1)
{
Debug.Log("[Cuong] 6");
// Chat_GameSession.AUICTranslate trans;
// EC_Game.GetGameRun().GetUIManager().GetInGameUIMan().AddHeartBeatHint(trans.Translate(szMsg ));
}
else
{
Debug.Log("[Cuong] Other");
CECStringTab pStrTab = EC_Game.GetFixedMsgs();
if (ISPLAYERID(p.Srcroleid))
{
string szName = EC_Game.GetGameRun().GetPlayerName(p.Srcroleid, false);
if (string.IsNullOrEmpty(strTemp))
{
Debug.Log("[Cuong] Other 0");
if (!bCalledagain)
{
Chat_GameSession.AddElemForPendingProtocols(pProtocol);
Chat_GameSession.AddChatPlayerID(p.Srcroleid);
}
return false;
}
else
{
Debug.Log("[Cuong] Other 1");
char[] szText = new char[80];
AUICommon.AUI_ConvertChatString(ref szName,ref szText, false);
string fmt = AUICommon.ConvertPrintfToCSharpFormat(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT));
string str = string.Format(
fmt,
szName,
strTemp
);
// Convert to EventBus
/*EC_Game.GetGameRun().AddChatMessage(strTemp, p.Channel, p.Srcroleid,
null, 0, p.Emotion, null, strMsgOrigion);*/
EventBus.Publish(new ChatMessageEvent(str, p.Channel));
// Set player's last said words
CECPlayer pPlayer = EC_Game.GetGameRun().GetWorld().GetPlayerMan().GetPlayer(p.Srcroleid);
if (pPlayer != null)
{
EventBus.PublishChannel(p.Srcroleid, new EventChatMessageOnTopPlayer(p.Srcroleid, strTemp));
//pPlayer.SetLastSaidWords(strTemp, p.Emotion);
}
}
}
else if(ISNPCID(p.Srcroleid))
{
Debug.Log("[Cuong] ISNPCID " + strTemp);
CECNPC pNPC = EC_Game.GetGameRun().GetWorld().GetNPCMan().GetNPC(p.Srcroleid);
if (pNPC != null)
{
string str;
string template = AUICommon.ConvertPrintfToCSharpFormat(pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT2));
string message = string.Format(
template,
pNPC.GetName(),
strTemp
);
EC_Game.GetGameRun().AddChatMessage(
message,
p.Channel,
p.Srcroleid,
null,
0,
p.Emotion
);
EventBus.Publish(new ChatMessageEvent(message));
CECUIHelper.RemoveNameFlagFromNPCChat(strTemp, out szMsg);
pNPC.SetLastSaidWords(szMsg);
}
}
}
return true;
}
public struct ChatMessageEvent
{
public string context;
public byte channel;
public ChatMessageEvent(string context)
public ChatMessageEvent(string context, byte channel = 0)
{
this.context = context;
this.channel = channel;
}
}
public void OnPrtcGetConfigRe(Protocol pProtocol)
@@ -1733,7 +2094,7 @@ namespace CSNetwork
EC_Game.GetConfigs().ApplyUserSetting();
}
// Now, Get config data request is sent after all host initial data ready.
// Now, Get config data request is sent after all host initial data ready.
// so when we receive this reply, we can do some last work before game
// really starts. Maybe it's not the best place to do these work, but
// now we do it here.
@@ -1744,7 +2105,7 @@ namespace CSNetwork
pGameUI.EnableUI(true);
// Get referral name for adding friend or other display
//TODO: a Hung lam phan select role info di
//TODO: a Hung lam phan select role info di
/* RoleInfo info = EC_Game.GetGameRun().GetSelectedRoleInfo();
if (info.referrer_role > 0)
GetPlayerBriefInfo(1, info.referrer_role, 2);*/
@@ -1765,7 +2126,7 @@ namespace CSNetwork
CECMCDownload::GetInstance().SendGetDownloadOK();*/
}
}
private void OnPrtcSetConfigRe(Protocol pProtocol)
{
SetUIConfig_Re p = (SetUIConfig_Re)pProtocol;
@@ -1775,12 +2136,12 @@ namespace CSNetwork
if (CECGameRun.Instance != null)
{
TestLogoutLogic.Instance.WasClientSendLogoutMessage = true;
CECGameRun.Instance.GetPendingLogOut().TriggerAll();
CECGameRun.Instance.GetPendingLogOut().Clear();
}
}
private void OnPrtcPlayerBaseInfoRe(Protocol pProtocol)
{
playerbaseinfo_re p = (playerbaseinfo_re)pProtocol;
@@ -1898,7 +2259,7 @@ namespace CSNetwork
BYTE* pBuf = (BYTE*)a_malloctemp(iSize);
if (!pBuf)
return;
((cmd_header*)pBuf)->cmd = C2S::GET_OTHER_EQUIP;
cmd_get_other_equip* pCmd = (cmd_get_other_equip*)(pBuf + sizeof (cmd_header));
@@ -1915,7 +2276,7 @@ namespace CSNetwork
var idlist = new int[iNumSend];
for (int i=0; i < iNumSend; i++)
idlist[i] = aIDs[iCount+i];
gamedatasend gamedatasend = new gamedatasend();
gamedatasend.Data = C2SCommandFactory.CreateGetOtherEquipCmd(iNumID, idlist);
SendProtocol(gamedatasend);
@@ -2031,7 +2392,7 @@ namespace CSNetwork
gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.STAND_UP);
SendProtocol(gamedatasend);
}
public void c2s_SendCmdAutoTeamSetGoal(int type, int goal_id, int op)
{
gamedatasend gamedatasend = new gamedatasend();
@@ -2074,6 +2435,43 @@ namespace CSNetwork
SendProtocol(g);
}
/// <summary>Send PROTOCOL_ADDFRIEND(202). Port of CECGameSession::friend_Add(idPlayer, szName).</summary>
public void Friend_Add(int idPlayer, string name)
{
var p = new addfriend();
p.Srcroleid = m_iCharID;
p.Dstroleid = idPlayer;
if (!string.IsNullOrEmpty(name))
p.Dstname = new Octets(System.Text.Encoding.Unicode.GetBytes(name));
else
p.Dstname = new Octets();
p.Srclsid = (int)_localsid;
SendProtocol(p);
}
/// <summary>
/// Send RPC_ADDFRIENDRQST(204) response. Port of CECGameSession::friend_AddResponse(dwHandle, bAgree).
/// Answer to received friend request: xid from request, agree = true (0) or false (69).
/// Matches GNET Rpc XID: low bit = is_request; response must clear that bit.
/// </summary>
public void Friend_AddResponse(uint xid, bool agree)
{
const byte ERR_TRADE_AGREE = 0;
const byte ERR_TRADE_REFUSE = 69;
uint responseXid = xid & 0x7FFFFFFFu; // clear request bit, keep count
var p = new addfriendrqstres(responseXid, agree ? ERR_TRADE_AGREE : ERR_TRADE_REFUSE);
SendProtocol(p);
}
/// <summary>Send PROTOCOL_GETFRIENDS(206). Port of CECGameSession::friend_GetList().</summary>
public void Friend_GetList()
{
var p = new getfriends();
p.Roleid = m_iCharID;
p.Localsid = (int)_localsid;
SendProtocol(p);
}
public void c2s_SendCmdTeamKickMember(int idMember)
{
var g = new gamedatasend();
@@ -2082,7 +2480,7 @@ namespace CSNetwork
}
public void c2s_SendCmdTeamLeaveParty()
{
{
var g = new gamedatasend();
g.Data = C2SCommandFactory.CreateTeamLeavePartyCommand();
SendProtocol(g);
@@ -2119,9 +2517,9 @@ namespace CSNetwork
public void c2s_CmdGoto(float x, float y, float z)
{
c2s_SendCmdGoto(x, y, z);
c2s_SendCmdGoto(x, y, z);
}
// Send C2S::GOTO command data
void c2s_SendCmdGoto(float x, float y, float z)
{
@@ -2248,7 +2646,7 @@ namespace CSNetwork
gamedatasend.Data = C2SCommandFactory.CreateNPCSevRestorePetCmd(iPetIdx);
SendProtocol(gamedatasend);
}
// Cross-server get in (C++: c2s_CmdNPCSevCrossServerGetIn) — TODO: implement C2S packet when needed
public void c2s_CmdNPCSevCrossServerGetIn()
{
@@ -2260,6 +2658,6 @@ namespace CSNetwork
{
// TODO: C2SCommandFactory.CreateNPCSevCrossServerGetOutCmd() and SendProtocol
}
}
}
}
@@ -109,6 +109,23 @@ namespace CSNetwork
_position += 4;
}
/// <summary>Write int32 in little-endian (for GNET protocol compatibility with C++ client/server).</summary>
public void WriteInt32LE(int value)
{
var bytes = BitConverter.GetBytes(value);
_octets.Insert(_position, bytes);
_position += 4;
}
/// <summary>Read int32 in little-endian (for GNET protocol compatibility with C++ client/server).</summary>
public int ReadInt32LE()
{
if (_position + 4 > _octets.Length)
throw new IndexOutOfRangeException("Attempt to read beyond the end of the stream.");
var bytes = ReadBytes(4);
return BitConverter.ToInt32(bytes, 0);
}
public void Write(float value)
{
var bytes = BitConverter.GetBytes(value);
@@ -0,0 +1,43 @@
using CSNetwork.Protocols.RPCData;
namespace CSNetwork.Protocols
{
/// <summary>PROTOCOL_ADDFRIEND_RE(203). Port of GNET::AddFriend_Re (inl/addfriend_re).</summary>
public class addfriend_re : Protocol
{
public byte retcode { get; set; }
public GFriendInfo info { get; set; }
public uint srclsid { get; set; }
public addfriend_re() : base(ProtocolType.PROTOCOL_ADDFRIEND_RE)
{
info = new GFriendInfo();
}
public override Protocol Clone() => new addfriend_re
{
retcode = retcode,
info = info != null ? new GFriendInfo { rid = info.rid, cls = info.cls, gid = info.gid, name = new Octets(info.name?.ToArray() ?? System.Array.Empty<byte>()) } : new GFriendInfo(),
srclsid = srclsid
};
public override void Marshal(OctetsStream os)
{
os.Write(retcode);
if (info != null) info.Marshal(os); else new GFriendInfo().Marshal(os);
os.Write(srclsid);
}
public override void Unmarshal(OctetsStream os)
{
retcode = os.ReadByte();
info = new GFriendInfo();
info.Unmarshal(os);
srclsid = os.ReadUInt32();
}
public override int PriorPolicy() => 1;
public override bool SizePolicy(int size) => size <= 128;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 158e35cf46fa51a4cba4fa7485fdb534
@@ -0,0 +1,47 @@
using System;
namespace CSNetwork.Protocols
{
/// <summary>RPC_ADDFRIENDRQST(204). Port of GNET AddFriendRqst (argument + xid). Server notifies target of friend request. Body: xid (4) + AddFriendRqstArg.</summary>
public class addfriendrqst : Protocol
{
/// <summary>RPC transaction id; must be sent back in addfriendrqstres so server can match the response.</summary>
public uint Xid { get; set; }
public int Srcroleid { get; set; }
public Octets Srcname { get; set; }
public uint Dstlsid { get; set; }
public addfriendrqst() : base(ProtocolType.RPC_ADDFRIENDRQST)
{
Srcname = new Octets();
}
public override Protocol Clone() => new addfriendrqst
{
Xid = Xid,
Srcroleid = Srcroleid,
Srcname = new Octets(Srcname.ToArray()),
Dstlsid = Dstlsid
};
public override void Marshal(OctetsStream os)
{
os.Write(Xid);
os.Write(Srcroleid);
os.Write(Srcname);
os.Write(Dstlsid);
}
public override void Unmarshal(OctetsStream os)
{
Xid = os.ReadUInt32();
Srcroleid = os.ReadInt32();
Srcname = os.ReadOctets();
Dstlsid = os.ReadUInt32();
}
public override int PriorPolicy() => 1;
public override bool SizePolicy(int size) => size <= 256;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bf82fc1afd675674588f017b6f9e0621
@@ -0,0 +1,44 @@
using System;
namespace CSNetwork.Protocols
{
/// <summary>
/// RPC_ADDFRIENDRQST(204) response. Port of GNET Rpc marshal (xid + AddFriendRqstRes).
/// Client sends this to answer a friend request: retcode 0 = agree, 69 = refuse (ERR_TRADE_AGREE / ERR_TRADE_REFUSE).
/// </summary>
public class addfriendrqstres : Protocol
{
/// <summary>RPC transaction id from the received addfriendrqst; server uses it to match the response.</summary>
public uint Xid { get; set; }
/// <summary>0 = ERR_TRADE_AGREE (accept), 69 = ERR_TRADE_REFUSE (decline).</summary>
public byte retcode { get; set; }
public addfriendrqstres() : base(ProtocolType.RPC_ADDFRIENDRQST)
{
}
public addfriendrqstres(uint xid, byte retcode) : base(ProtocolType.RPC_ADDFRIENDRQST)
{
Xid = xid;
this.retcode = retcode;
}
public override Protocol Clone() => new addfriendrqstres(Xid, retcode);
public override void Marshal(OctetsStream os)
{
os.Write(Xid);
os.Write(retcode);
}
public override void Unmarshal(OctetsStream os)
{
Xid = os.ReadUInt32();
retcode = os.ReadByte();
}
public override int PriorPolicy() => 1;
public override bool SizePolicy(int size) => size <= 64;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 80bb3b37b64736040a16d67848d9f953
@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using CSNetwork.Protocols.RPCData;
namespace CSNetwork.Protocols
{
public class friendextlist : Protocol
{
public int Roleid { get; set; }
public List<GFriendExtInfo> ExtraInfo { get; set; } = new List<GFriendExtInfo>();
public List<GSendAUMailRecord> SendInfo { get; set; } = new List<GSendAUMailRecord>();
public int Localsid { get; set; }
public friendextlist() : base(ProtocolType.PROTOCOL_FRIENDEXTLIST)
@@ -16,18 +19,26 @@ namespace CSNetwork.Protocols
public override Protocol Clone() => new friendextlist
{
Roleid = Roleid,
ExtraInfo = new List<GFriendExtInfo>(ExtraInfo ?? new List<GFriendExtInfo>()),
SendInfo = new List<GSendAUMailRecord>(SendInfo ?? new List<GSendAUMailRecord>()),
Localsid = Localsid
};
public override void Marshal(OctetsStream os)
{
os.Write(Roleid);
os.WriteList(ExtraInfo ?? new List<GFriendExtInfo>());
os.WriteList(SendInfo ?? new List<GSendAUMailRecord>());
os.Write(Localsid);
}
public override void Unmarshal(OctetsStream os)
{
Roleid = os.ReadInt32();
ExtraInfo = new List<GFriendExtInfo>();
os.ReadList(ExtraInfo);
SendInfo = new List<GSendAUMailRecord>();
os.ReadList(SendInfo);
Localsid = os.ReadInt32();
}
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using CSNetwork.Protocols.RPCData;
namespace CSNetwork.Protocols
{
/// <summary>PROTOCOL_GETFRIENDS_RE(207). Server response to getfriends(206).</summary>
public class getfriends_re : Protocol
{
public int Roleid { get; set; }
public List<GGroupInfo> Groups { get; set; } = new List<GGroupInfo>();
public List<GFriendInfo> Friends { get; set; } = new List<GFriendInfo>();
public List<byte> Status { get; set; } = new List<byte>();
public int Localsid { get; set; }
public getfriends_re() : base(ProtocolType.PROTOCOL_GETFRIENDS_RE)
{
}
public override Protocol Clone() => new getfriends_re
{
Roleid = Roleid,
Groups = new List<GGroupInfo>(Groups ?? new List<GGroupInfo>()),
Friends = new List<GFriendInfo>(Friends ?? new List<GFriendInfo>()),
Status = new List<byte>(Status ?? new List<byte>()),
Localsid = Localsid
};
public override void Marshal(OctetsStream os)
{
os.Write(Roleid);
os.WriteList(Groups ?? new List<GGroupInfo>());
os.WriteList(Friends ?? new List<GFriendInfo>());
os.WriteCompactUInt((uint)(Status?.Count ?? 0));
if (Status != null)
{
for (int i = 0; i < Status.Count; i++)
os.Write(Status[i]);
}
os.Write(Localsid);
}
public override void Unmarshal(OctetsStream os)
{
Roleid = os.ReadInt32();
Groups = new List<GGroupInfo>();
os.ReadList(Groups);
Friends = new List<GFriendInfo>();
os.ReadList(Friends);
uint sc = os.ReadCompactUInt();
Status = new List<byte>((int)sc);
for (int i = 0; i < sc; i++)
Status.Add(os.ReadByte());
Localsid = os.ReadInt32();
}
public override int PriorPolicy() => 1;
public override bool SizePolicy(int size) => size <= 8192;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bd80f916689adb4488d055af7d1f0465
@@ -0,0 +1,51 @@
namespace CSNetwork.Protocols.RPCData
{
/// <summary>Port of GNET::GFriendExtInfo (rpcdata/gfriendextinfo).</summary>
public class GFriendExtInfo : IMarshallable
{
public int uid;
public int rid;
public int level;
public int last_logintime;
public int update_time;
public byte reincarnation_times;
public Octets remarks;
public short reserved1;
public int reserved2;
public int reserved3;
public GFriendExtInfo()
{
remarks = new Octets();
}
public void Marshal(OctetsStream os)
{
os.Write(uid);
os.Write(rid);
os.Write(level);
os.Write(last_logintime);
os.Write(update_time);
os.Write(reincarnation_times);
os.Write(remarks ?? new Octets());
os.Write(reserved1);
os.Write(reserved2);
os.Write(reserved3);
}
public void Unmarshal(OctetsStream os)
{
uid = os.ReadInt32();
rid = os.ReadInt32();
level = os.ReadInt32();
last_logintime = os.ReadInt32();
update_time = os.ReadInt32();
reincarnation_times = os.ReadByte();
remarks = os.ReadOctets();
reserved1 = os.ReadInt16();
reserved2 = os.ReadInt32();
reserved3 = os.ReadInt32();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cbd227cebee40b0438f44b5a52f47459
@@ -0,0 +1,32 @@
namespace CSNetwork.Protocols.RPCData
{
/// <summary>Port of GNET::GFriendInfo (rpcdata/gfriendinfo).</summary>
public class GFriendInfo : IMarshallable
{
public int rid;
public byte cls;
public byte gid;
public Octets name;
public GFriendInfo()
{
name = new Octets();
}
public void Marshal(OctetsStream os)
{
os.Write(rid);
os.Write(cls);
os.Write(gid);
os.Write(name ?? new Octets());
}
public void Unmarshal(OctetsStream os)
{
rid = os.ReadInt32();
cls = os.ReadByte();
gid = os.ReadByte();
name = os.ReadOctets();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a0bf31ab9d8853a479215aa7e1fa5e94
@@ -0,0 +1,27 @@
namespace CSNetwork.Protocols.RPCData
{
/// <summary>Port of GNET::GGroupInfo (rpcdata/ggroupinfo).</summary>
public class GGroupInfo : IMarshallable
{
public byte gid;
public Octets name;
public GGroupInfo()
{
name = new Octets();
}
public void Marshal(OctetsStream os)
{
os.Write(gid);
os.Write(name ?? new Octets());
}
public void Unmarshal(OctetsStream os)
{
gid = os.ReadByte();
name = os.ReadOctets();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 14af8f8307d31b94585bf4526fcdc1b1
@@ -0,0 +1,22 @@
namespace CSNetwork.Protocols.RPCData
{
/// <summary>Port of GNET::GSendAUMailRecord (rpcdata/gsendaumailrecord).</summary>
public class GSendAUMailRecord : IMarshallable
{
public int rid;
public int sendmail_time;
public void Marshal(OctetsStream os)
{
os.Write(rid);
os.Write(sendmail_time);
}
public void Unmarshal(OctetsStream os)
{
rid = os.ReadInt32();
sendmail_time = os.ReadInt32();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c573cfeb052d9f840891d71dfbaecd6a
@@ -163,6 +163,9 @@ namespace BrewMonster.Network
{
BaseSecurity.Initizalize();
ProtocolFactory.RegisterAllProtocols();
// Type 204 is used for both addfriendrqst (server→client request) and addfriendrqstres (client→server response).
// Client only receives addfriendrqst; ensure decode uses it so we don't get InvalidCastException.
Protocol.Register<addfriendrqst>((uint)ProtocolType.RPC_ADDFRIENDRQST);
_gameSession = new GameSession();
#if UNITY_EDITOR
var path = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Assets"));
@@ -173,7 +176,9 @@ namespace BrewMonster.Network
// Subscribe to unexpected disconnects
_gameSession.Disconnected += OnUnexpectedDisconnect;
_gameSession.FriendRequestReceived += OnFriendRequestReceived;
_gameSession.AddFriendResultReceived += OnAddFriendResultReceived;
_isInitialized = true;
DontDestroyOnLoad(gameObject);
@@ -184,6 +189,8 @@ namespace BrewMonster.Network
// Tell LoginScene what to show next.
LogoutFlowState.NextLoginEntry = entryTarget;
_gameSession.Disconnected -= OnUnexpectedDisconnect;
_gameSession.FriendRequestReceived -= OnFriendRequestReceived;
_gameSession.AddFriendResultReceived -= OnAddFriendResultReceived;
EC_ManMessageMono.Instance.CECNPCMan.Release();
if (clearSavedCreds)
@@ -632,6 +639,20 @@ namespace BrewMonster.Network
{
Instance._gameSession.c2s_SendCmdDuelReply(accept, idInviter);
}
/// <summary>Send PROTOCOL_ADDFRIEND(202). Port of CECGameSession::friend_Add.</summary>
public static void Friend_Add(int idPlayer, string name)
{
Instance._gameSession.Friend_Add(idPlayer, name ?? "");
}
/// <summary>Send PROTOCOL_GETFRIENDS(206). Port of CECGameSession::friend_GetList().</summary>
public static void Friend_GetList()
{
Instance._gameSession.Friend_GetList();
}
public static void Friend_AddResponse(uint xid, bool agree)
{
Instance._gameSession.Friend_AddResponse(xid, agree);
}
public static void c2s_CmdTeamKickMember(int idMember)
{
Instance._gameSession.c2s_SendCmdTeamKickMember(idMember);
@@ -712,6 +733,26 @@ namespace BrewMonster.Network
/// <summary>
/// Handles unexpected server disconnections. Shows a message box and returns to login.
/// </summary>
private void OnFriendRequestReceived(uint xid, int srcroleid, string askerName)
{
string name = string.IsNullOrEmpty(askerName) ? ("Player " + srcroleid) : askerName;
CECUIManager.Instance?.ShowMessageBoxYesAndNo(
title: "Friend Request",
message: $"{name} wants to add you as a friend.",
dlg: null,
onClickedYes: () => Friend_AddResponse(xid, agree: true),
onClickedNo: () => Friend_AddResponse(xid, agree: false));
}
private void OnAddFriendResultReceived(byte retcode, string message)
{
CECUIManager.Instance?.ShowMessageBoxYes(
title: retcode == 0 ? "Friend added" : "Add friend failed",
message: message,
dlg: null,
null);
}
private void OnUnexpectedDisconnect()
{
// If this was an intentional disconnect (logout), skip UI
@@ -722,16 +763,13 @@ namespace BrewMonster.Network
}
// Show disconnect message box
CECUIManager.Instance?.ShowMessageBox(
title: "Disconnected",
message: "Connection to the server has been lost.",
messageBoxType: MessageBoxType.YesButton,
onClickedYes: () =>
CECUIManager.Instance?.ShowMessageBoxYes("Disconnected", "Connection to the server has been lost.", null,
() =>
{
// Return to login screen
LogoutAccount();
}
);
});
}
public static void c2s_CmdGoto(float x, float y, float z)
@@ -2,6 +2,7 @@ using BrewMonster;
using BrewMonster.Managers;
using BrewMonster.Network;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Skills;
using CSNetwork;
using CSNetwork.GPDataType;
using CSNetwork.Protocols;
@@ -452,13 +453,51 @@ namespace BrewMonster
case EC_MsgDef.MSG_PM_PLAYERBASEINFO: OnMsgPlayerBaseInfo(Msg); break;
case EC_MsgDef.MSG_PM_PLAYEREQUIPDATA: OnMsgPlayerEquipData(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERATKRESULT: OnMsgPlayerAtkResult(Msg); break;
case EC_MsgDef.MSG_PM_CASTSKILL: OnMsgPlayerCastSkill(Msg); break;
case EC_MsgDef.MSG_PM_ENCHANTRESULT: OnMsgEnchantResult(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERDOEMOTE: OnMsgPlayerDoEmote(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERSKILLRESULT: OnMsgPlayerSkillResult(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERGATHER: OnMsgPlayerGather(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERMOUNT: OnMsgPlayerMount(Msg); break;
}
return true;
}
/// <summary>
/// Handles skill attack result messages for else players.
///
/// This method is called when the server sends MSG_PM_PLAYERSKILLRESULT (OBJECT_SKILL_ATTACK_RESULT).
/// Unlike OnMsgPlayerAtkResult which handles melee attacks, this handles skill attacks specifically.
///
/// Flow:
/// 1. Parse cmd_object_skill_attack_result (includes skill_id directly)
/// 2. Face target
/// 3. Call PlayAttackEffect with skill_id from command (triggers GFX system)
/// 4. Enter fight state if skill is attack or curse type
///
/// C++ equivalent: CECElsePlayer::OnMsgPlayerSkillResult (EC_ElsePlayer.cpp:1787)
/// </summary>
private void OnMsgPlayerSkillResult(ECMSG Msg)
{
cmd_object_skill_attack_result pCmd = GPDataTypeHelper.FromBytes<cmd_object_skill_attack_result>((byte[])Msg.dwParam1);
// Face to target
TurnFaceTo(pCmd.target_id);
// Call PlayAttackEffect with skill_id directly from command (like C++ does)
// Unlike OnMsgPlayerAtkResult, we get skill_id from the command structure, not from m_pCurSkill
int attackTime = int.MinValue;
PlayAttackEffect(pCmd.target_id, pCmd.skill_id, 0, -1,
(uint)pCmd.attack_flag, pCmd.speed * 50, ref attackTime, pCmd.section);
// Check skill type and enter fight state if needed (matching C++ logic)
byte skillType = ElementSkill.GetType((uint)pCmd.skill_id);
if (skillType == (byte)skill_type.TYPE_ATTACK || skillType == (byte)skill_type.TYPE_CURSE)
{
EnterFightState();
}
}
public void HandleRevive(short sReviveType, A3DVECTOR3 pos)
{
SetServerPos(pos);
@@ -494,7 +533,7 @@ namespace BrewMonster
}
}
public void OnMsgPlayerEquipData(ECMSG Msg)
public async void OnMsgPlayerEquipData(ECMSG Msg)
{
// using namespace S2C;
@@ -528,12 +567,32 @@ namespace BrewMonster
}
// // Change equipment
ChangeEquipments(bReset, crc, iAddMask, iDelMask, aAdded);
// ChangeEquipments(bReset, crc, iAddMask, iDelMask, aAdded);
await QueueChangeEquipments(bReset, crc, iAddMask, iDelMask, aAdded);
}
private async UniTask QueueChangeEquipments(bool bReset, int crc, long iAddMask, long iDelMask, int[] aAddedEquip)
{
while (!IsPlayerModelReady)
{
await UniTask.DelayFrame(1);
}
ChangeEquipments(bReset, crc, iAddMask, iDelMask, aAddedEquip);
}
/// <summary>
/// Handles attack result messages for else players (both melee and skill attacks).
///
/// For skill attacks:
/// - Uses m_pCurSkill (set in OnMsgPlayerCastSkill) to get skill ID
/// - Calls PlayAttackEffect with skill ID, which triggers GFX system
/// - GFX system (A3DSkillGfxComposerMan) spawns effects at hook positions
///
/// For melee attacks:
/// - idSkill is 0, triggers normal melee attack work
/// </summary>
void OnMsgPlayerAtkResult(ECMSG Msg)
{
cmd_object_atk_result pCmd = GPDataTypeHelper.FromBytes<cmd_object_atk_result>((byte[])Msg.dwParam1);
//ASSERT(pCmd && pCmd.attacker_id == m_PlayerInfo.cid);
@@ -542,16 +601,142 @@ namespace BrewMonster
// TO DO: fix later
int attackTime = int.MinValue;
PlayAttackEffect(pCmd.target_id, 0, 0, -1, (uint)pCmd.attack_flag, pCmd.speed* 50, ref attackTime);
PlayAttackEffect(pCmd.target_id, 0, 0, -1, (uint)pCmd.attack_flag, pCmd.speed * 50, ref attackTime);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] PlayAttackEffect: Complete, attackTime={attackTime}");
if (!m_pEPWorkMan.FindWork(CECEPWorkMan.Work_type.WT_NORMAL, CECEPWork.EP_work_ID.WORK_HACKOBJECT)){
m_pEPWorkMan.StartNormalWork(new CECEPWorkMelee(m_pEPWorkMan, pCmd.target_id));
}
if (!m_pEPWorkMan.FindWork(CECEPWorkMan.Work_type.WT_NORMAL, CECEPWork.EP_work_ID.WORK_HACKOBJECT))
{
m_pEPWorkMan.StartNormalWork(new CECEPWorkMelee(m_pEPWorkMan, pCmd.target_id));
}
// Enter fight state
EnterFightState();
}
/// <summary>
/// Handles skill casting messages from server for else players.
///
/// Flow:
/// 1. Server sends OBJECT_CAST_SKILL -> This handler plays cast animation
/// 2. Server sends SKILL_PERFORM -> Skill execution begins (for durative skills)
/// 3. Server sends attack result -> OnMsgPlayerAtkResult triggers PlayAttackEffect
/// 4. PlayAttackEffect -> CECAttacksMan.AddSkillAttack -> GFX system spawns effects
/// 5. Server sends SKILL_INTERRUPTED -> Clears casting state (if interrupted)
///
/// Note: Else players don't maintain skill lists, so we create temporary CECSkill objects
/// for tracking purposes only. The actual skill data comes from ElementSkill static methods.
/// </summary>
private void OnMsgPlayerCastSkill(ECMSG Msg)
{
int commandID = Convert.ToInt32(Msg.dwParam2);
switch (commandID)
{
case CommandID.OBJECT_CAST_SKILL:
{
cmd_object_cast_skill pCmd =
GPDataTypeHelper.FromBytes<cmd_object_cast_skill>((byte[])Msg.dwParam1);
// Get skill object (else players don't have skill lists, so we create a temporary skill reference)
// For else players, we mainly need the skill ID for animation purposes
int skillID = pCmd.skill;
// Store current skill target
m_idCurSkillTarget = pCmd.target;
// Face the target
TurnFaceTo(pCmd.target);
// Play skill cast animation
PlaySkillCastAction(skillID);
// Create a temporary skill object for tracking (if needed)
// Note: Else players don't maintain skill lists like host player does
// We create a minimal skill object just for the current cast
if (m_pCurSkill == null || m_pCurSkill.GetSkillID() != skillID)
{
// Create a temporary skill object with level 1 (we don't know the actual level)
m_pCurSkill = new CECSkill(skillID, 1);
}
// Enter fight state
EnterFightState();
break;
}
case CommandID.OBJECT_CAST_INSTANT_SKILL:
{
cmd_object_cast_instant_skill pCmd =
GPDataTypeHelper.FromBytes<cmd_object_cast_instant_skill>((byte[])Msg.dwParam1);
int skillID = pCmd.skill;
m_idCurSkillTarget = pCmd.target;
TurnFaceTo(pCmd.target);
PlaySkillCastAction(skillID);
if (m_pCurSkill == null || m_pCurSkill.GetSkillID() != skillID)
{
m_pCurSkill = new CECSkill(skillID, 1);
}
EnterFightState();
break;
}
case CommandID.OBJECT_CAST_POS_SKILL:
{
cmd_object_cast_pos_skill pCmd =
GPDataTypeHelper.FromBytes<cmd_object_cast_pos_skill>((byte[])Msg.dwParam1);
int skillID = pCmd.skill;
// For position-based skills, target is the position, not an object
// We still play the cast animation
PlaySkillCastAction(skillID);
if (m_pCurSkill == null || m_pCurSkill.GetSkillID() != skillID)
{
m_pCurSkill = new CECSkill(skillID, 1);
}
EnterFightState();
break;
}
case CommandID.SKILL_PERFORM:
{
// Skill perform - the skill has finished casting and is being executed
// For else players, we keep m_pCurSkill until attack result is received
// This allows PlayAttackEffect to use the skill information
// Durative skills (channeling) will continue until interrupted
if (m_pCurSkill != null && m_pCurSkill.IsDurative())
{
// For durative skills, we keep the skill active
// It will be cleared when SKILL_INTERRUPTED is received
}
break;
}
case CommandID.SKILL_INTERRUPTED:
{
// Skill was interrupted, clear current skill
cmd_skill_interrupted pCmd =
GPDataTypeHelper.FromBytes<cmd_skill_interrupted>((byte[])Msg.dwParam1);
if (m_pCurSkill != null)
{
StopSkillCastAction();
m_pCurSkill = null;
}
m_idCurSkillTarget = 0;
break;
}
default:
{
break;
}
}
}
private async void LoadAppearGfx()
{
if (!m_pAppearGFX && m_iAppearFlag == (int)PlayerAppearFlag.APPEAR_ENTERWORLD)
@@ -809,6 +994,7 @@ namespace BrewMonster
m_iGender = iGender;
m_bBaseInfoReady = true;
SetPlayerName(szName ?? "");
EC_Game.GetGameRun().AddPlayerName(m_PlayerInfo.cid, szName, true);
}
// Level up
public void LevelUp()
@@ -120,7 +120,6 @@ namespace BrewMonster
m_AttFlyMode = (GfxAttackMode)0;
m_AttHitMode = (GfxAttackMode)0;
m_dwFlyTime = 200;
m_bTraceTarget = true;
m_FlyClusterCount = 1;
m_FlyClusterInterval = 0;
m_HitClusterCount = 1;
+110 -30
View File
@@ -1,8 +1,9 @@
using System;
using System;
using BrewMonster.UI;
using PerfectWorld.Scripts.Common;
using UnityEngine;
using TMPro;
using UnityEngine.Serialization;
using UnityEngine.UI;
using static CECUIManager;
@@ -27,16 +28,19 @@ namespace BrewMonster
{
[SerializeField] private TMP_Text titleText;
[SerializeField] private TMP_Text messageText;
[SerializeField] private Button okButton;
[SerializeField] private Button _yesButton;
[SerializeField] private Button _noButton;
[SerializeField] private Button _closeButton;
private Action _onClickedYesBtn;
private Action _onClickedNoBtn;
private MessageBoxData _messageData;
public override void OnEnable()
{
base.OnEnable();
okButton.onClick.AddListener(OnOkClicked);
_yesButton.onClick.AddListener(OnYesClicked);
_noButton.onClick.AddListener(OnNoClicked);
_closeButton.onClick.AddListener(OnCloseClicked);
@@ -44,59 +48,135 @@ namespace BrewMonster
public override void OnDisable()
{
okButton.onClick.RemoveListener(OnOkClicked);
_yesButton.onClick.RemoveListener(OnYesClicked);
_noButton.onClick.RemoveListener(OnNoClicked);
_closeButton.onClick.RemoveListener(OnCloseClicked);
}
#region Button Events
private void OnOkClicked()
private void OnYesClicked()
{
EventBus.Publish(new MessageBoxEvent(1,_messageData.Dlg));
_messageData.OnClickedYes?.Invoke();
_onClickedYesBtn?.Invoke();
Show(false);
}
private void OnNoClicked()
{
EventBus.Publish(new MessageBoxEvent(0,_messageData.Dlg));
_messageData.OnClickedNo?.Invoke();
_onClickedNoBtn?.Invoke();
Show(false);
}
private void OnCloseClicked()
{
// treat the close button as OK button
OnOkClicked();
OnYesClicked();
}
#endregion
public void ShowMessageBox(MessageBoxData messageBoxData)
// public void ShowMessageBox(MessageBoxData messageBoxData)
// {
// _messageData = messageBoxData;
// messageBoxData.Message = EC_TextFormatter.FormatForTextMeshPro(messageBoxData.Message);
// SetName(string.IsNullOrEmpty(messageBoxData.Title) ? "" : messageBoxData.Title);
// messageText.text = string.IsNullOrEmpty(messageBoxData.Message) ? "" : messageBoxData.Message;
//
// _yesButton.gameObject.SetActive(false);
// _noButton.gameObject.SetActive(false);
// switch (_messageData.MessageBoxType)
// {
// case MessageBoxType.YesButton:
// _yesButton.gameObject.SetActive(true);
// break;
// case MessageBoxType.NoButton:
// _noButton.gameObject.SetActive(true);
// break;
// case MessageBoxType.BothYesNoButton:
// _yesButton.gameObject.SetActive(true);
// _noButton.gameObject.SetActive(true);
// break;
// }
// Show(true);
// }
/// <summary>
/// message with title and message only
/// </summary>
/// <param name="title"></param>
/// <param name="message"></param>
/// <param name="dlg"></param>
public void ShowMessageBoxGeneral(string title, string message, AUIDialog dlg)
{
_messageData = messageBoxData;
// messageBoxData.Message = messageBoxData.Message?
// .Replace("\r\n", "\n")
// .Replace("\r", "\n");
messageBoxData.Message = EC_TextFormatter.FormatForTextMeshPro(messageBoxData.Message);
SetName(string.IsNullOrEmpty(messageBoxData.Title) ? "" : messageBoxData.Title);
messageText.text = string.IsNullOrEmpty(messageBoxData.Message) ? "" : messageBoxData.Message;
okButton.gameObject.SetActive(false);
_onClickedYesBtn = null;
_onClickedYesBtn = null;
string formattedMessage = EC_TextFormatter.FormatForTextMeshPro(message);
SetName(string.IsNullOrEmpty(title) ? "" : title);
messageText.text = string.IsNullOrEmpty(formattedMessage) ? "" : formattedMessage;
_noButton.gameObject.SetActive(false);
switch (_messageData.MessageBoxType)
{
case MessageBoxType.YesButton:
okButton.gameObject.SetActive(true);
break;
case MessageBoxType.NoButton:
_noButton.gameObject.SetActive(true);
break;
case MessageBoxType.BothYesNoButton:
okButton.gameObject.SetActive(true);
_noButton.gameObject.SetActive(true);
break;
}
_yesButton.gameObject.SetActive(true);
Show(true);
transform.SetAsLastSibling();
}
/// <summary>
/// message with yes button only
/// </summary>
/// <param name="title"></param>
/// <param name="message"></param>
/// <param name="dlg"></param>
/// <param name="onClickedYes"></param>
public void ShowMessageBoxYes(string title, string message, AUIDialog dlg, Action onClickedYes)
{
_onClickedYesBtn = onClickedYes;
string formattedMessage = EC_TextFormatter.FormatForTextMeshPro(message);
SetName(string.IsNullOrEmpty(title) ? "" : title);
messageText.text = string.IsNullOrEmpty(formattedMessage) ? "" : formattedMessage;
_noButton.gameObject.SetActive(false);
_yesButton.gameObject.SetActive(true);
Show(true);
transform.SetAsLastSibling();
}
/// <summary>
/// message with no button only
/// </summary>
/// <param name="title"></param>
/// <param name="message"></param>
/// <param name="dlg"></param>
/// <param name="onClickedNo"></param>
public void ShowMessageBoxNo(string title, string message, AUIDialog dlg, Action onClickedNo)
{
_onClickedNoBtn = onClickedNo;
string formattedMessage = EC_TextFormatter.FormatForTextMeshPro(message);
SetName(string.IsNullOrEmpty(title) ? "" : title);
messageText.text = string.IsNullOrEmpty(formattedMessage) ? "" : formattedMessage;
_yesButton.gameObject.SetActive(false);
_noButton.gameObject.SetActive(true);
Show(true);
transform.SetAsLastSibling();
}
/// <summary>
/// message with yes and no button
/// </summary>
/// <param name="title"></param>
/// <param name="message"></param>
/// <param name="dlg"></param>
/// <param name="onClickedYes"></param>
/// <param name="onClickedNo"></param>
public void ShowMessageBoxYesAndNo(string title, string message, AUIDialog dlg, Action onClickedYes, Action onClickedNo)
{
_onClickedYesBtn = onClickedYes;
_onClickedNoBtn = onClickedNo;
string formattedMessage = EC_TextFormatter.FormatForTextMeshPro(message);
SetName(string.IsNullOrEmpty(title) ? "" : title);
messageText.text = string.IsNullOrEmpty(formattedMessage) ? "" : formattedMessage;
_yesButton.gameObject.SetActive(true);
_noButton.gameObject.SetActive(true);
Show(true);
transform.SetAsLastSibling();
}
}
}
@@ -1,4 +1,4 @@
using BrewMonster.Network;
using BrewMonster.Network;
using BrewMonster.Scripts.Managers;
using BrewMonster.UI;
using PerfectWorld.Scripts.Managers;
@@ -37,12 +37,13 @@ namespace BrewMonster
[Header("Buttons and Money")]
[SerializeField]
private TextMeshProUGUI m_TxtMoney;
[SerializeField] private Button m_useItem;
[SerializeField] private Button m_BtnMergeOrReset;
[SerializeField] private Button m_BtnCancel;
[SerializeField] private Button m_BtnClose;
[SerializeField] private Sprite khung_item;
[SerializeField] private Transform itemInventoryRoot;
[SerializeField] private GameObject itemInventoryRoot;
private EC_IvtrItem m_SelectedEquip;
private EC_IvtrItem m_SelectedMaterial;
@@ -58,31 +59,14 @@ namespace BrewMonster
public override void Awake()
{
base.Awake();
RegisterDrop(m_SlotFirstParent, OnDropEquip);
RegisterClick(m_SlotFirstParent, OnClickEquipSlot);
if (m_Mode == InstallMode.Enchase && m_SlotSecondParent != null)
{
RegisterDrop(m_SlotSecondParent, OnDropMaterial);
RegisterClick(m_SlotSecondParent, OnClickMaterialSlot);
}
}
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
}
public override void OnEnable()
{
base.OnEnable();
@@ -91,6 +75,7 @@ namespace BrewMonster
m_BtnMergeOrReset.onClick.AddListener(OnClickedMergeOrReset);
m_BtnCancel.onClick.AddListener(OnCommandCancel);
m_BtnClose.onClick.AddListener(OnCommandCancel);
m_useItem.onClick.AddListener(OnUseItemClicked);
m_install_price = -1;
if (m_SlotSecondParent != null)
m_SlotSecondParent.gameObject.SetActive(m_Mode == InstallMode.Enchase);
@@ -104,6 +89,7 @@ namespace BrewMonster
m_BtnMergeOrReset.onClick.RemoveListener(OnClickedMergeOrReset);
m_BtnCancel.onClick.RemoveListener(OnCommandCancel);
m_BtnClose.onClick.RemoveListener(OnCommandCancel);
m_useItem.onClick.RemoveListener(OnUseItemClicked);
}
private void RestoreInventoryColors()
@@ -147,23 +133,6 @@ namespace BrewMonster
return list[slot];
}
private void RegisterDrop(Transform target, Action<PointerEventData> callback)
{
var trigger = target.GetComponent<EventTrigger>();
if (trigger == null)
trigger = target.gameObject.AddComponent<EventTrigger>();
trigger.triggers.Clear();
var entry = new EventTrigger.Entry
{
eventID = EventTriggerType.Drop
};
entry.callback.AddListener((data) => { callback((PointerEventData)data); });
trigger.triggers.Add(entry);
}
private void RegisterClick(Transform target, Action<PointerEventData> callback)
{
if (target == null) return;
@@ -199,117 +168,6 @@ namespace BrewMonster
}
}
private EC_IvtrItem GetItemFromDrag(PointerEventData eventData)
{
if (eventData.pointerDrag == null)
return null;
var btn = eventData.pointerDrag.GetComponent<Button>();
if (btn == null)
return null;
// Slot index
int slotIndex = btn.transform.GetSiblingIndex();
// Inventory package = 0
var host = CECGameRun.Instance?.GetHostPlayer();
if (host == null)
return null;
var inv = host.GetInventory(0);
if (inv == null)
return null;
return inv.GetItem(slotIndex, false);
}
private void OnDropEquip(PointerEventData eventData)
{
if (eventData.pointerDrag == null)
return;
var btn = eventData.pointerDrag.GetComponent<Button>();
if (btn == null)
return;
int slotIndex = btn.transform.GetSiblingIndex();
var item = GetItemFromDrag(eventData);
if (item == null)
return;
if(!item.IsEquipment())
return;
EC_IvtrItem detailedItem = EC_IvtrItem.CreateItem(item.m_tid, item.m_expire_date, item.m_iCount);
if (item.Content != null && item.Content.Length > 0)
detailedItem.SetItemInfo(item.Content, item.Content.Length);
else
detailedItem.GetDetailDataFromLocal();
if (m_FirstInvSlot >= 0)
{
var previosBtn = FindInventoryButtonBySlot(m_FirstInvSlot);
SetInventorySlotGray(previosBtn, false);
}
m_SelectedEquip?.Freeze(false);
m_SelectedEquip = detailedItem;
m_FirstInvSlot = slotIndex;
m_TxtFirstName.text = detailedItem.GetName();
SetSlotIcon(m_SlotFirstParent, detailedItem);
SetInventorySlotGray(btn, true);
detailedItem.Freeze(true);
UpdateResourceInfo();
}
private void OnDropMaterial(PointerEventData eventData)
{
if (eventData.pointerDrag == null)
return;
var btn = eventData.pointerDrag.GetComponent<Button>();
if (btn == null)
return;
int slotIndex = btn.transform.GetSiblingIndex();
var item = GetItemFromDrag(eventData);
if (item == null)
return;
if (item.GetClassID() != (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_STONE)
return;
EC_IvtrItem detailedItem = EC_IvtrItem.CreateItem(item.m_tid, item.m_expire_date, item.m_iCount);
if (item.Content != null && item.Content.Length > 0)
detailedItem.SetItemInfo(item.Content, item.Content.Length);
else
detailedItem.GetDetailDataFromLocal();
if (m_SecondInvSlot >= 0)
{
var previosBtn = FindInventoryButtonBySlot(m_SecondInvSlot);
SetInventorySlotGray(previosBtn, false);
}
m_SelectedMaterial?.Freeze(false);
m_SelectedMaterial = detailedItem;
m_SelectedMaterial?.Freeze(true);
m_SecondInvSlot = slotIndex;
m_TxtSecondName.text = detailedItem.GetName();
SetSlotIcon(m_SlotSecondParent, detailedItem);
SetInventorySlotGray(btn, true);
UpdateResourceInfo();
}
private void CalculateUninstallPrice(EC_IvtrItem equipment)
{
if (equipment == null || !equipment.IsEquipment())
@@ -431,11 +289,7 @@ namespace BrewMonster
if (nMoney > pHost.GetMoneyAmount())
{
message = GetGameUIMan().GetStringFromTable(226);
CECUIManager.Instance.ShowMessageBox(new MessageBoxData()
{
Message = message,
Dlg = this
});
CECUIManager.Instance.ShowMessageBoxGeneral("", message, this);
return;
}
@@ -443,11 +297,7 @@ namespace BrewMonster
if (!pIvtrA.IsEquipment())
{
message = GetGameUIMan().GetStringFromTable(223);
CECUIManager.Instance.ShowMessageBox(new MessageBoxData()
{
Message = message,
Dlg = this
});
CECUIManager.Instance.ShowMessageBoxGeneral("", message, this);
return;
}
@@ -457,11 +307,7 @@ namespace BrewMonster
if (pEquipA.GetEmptyHoleNum() <= 0)
{
message = GetGameUIMan().GetStringFromTable(224);
CECUIManager.Instance.ShowMessageBox(new MessageBoxData()
{
Message = message,
Dlg = this
});
CECUIManager.Instance.ShowMessageBoxGeneral("", message, this);
return;
}
@@ -469,11 +315,7 @@ namespace BrewMonster
if (pIvtrB == null || !pIvtrB.IsEmbeddable())
{
message = GetGameUIMan().GetStringFromTable(225);
CECUIManager.Instance.ShowMessageBox(new MessageBoxData()
{
Message = message,
Dlg = this
});
CECUIManager.Instance.ShowMessageBoxGeneral("", message, this);
return;
}
@@ -498,11 +340,7 @@ namespace BrewMonster
if (nStoneLevel > nEquipLevel)
{
message = GetGameUIMan().GetStringFromTable(300);
CECUIManager.Instance.ShowMessageBox(new MessageBoxData()
{
Message = message,
Dlg = this
});
CECUIManager.Instance.ShowMessageBoxGeneral("", message, this);
return;
}
@@ -515,46 +353,28 @@ namespace BrewMonster
pHost.GetPack(InventoryConst.IVTRTYPE_PACK).UnfreezeAllItems();
message = GetGameUIMan().GetStringFromTable(228);
CECUIManager.Instance.ShowMessageBox(new MessageBoxData()
{
Message = message,
Dlg = this
});
CECUIManager.Instance.ShowMessageBoxGeneral("", message, this);
}
else if (pHost != null && m_Mode == InstallMode.Disenchase)
{
if (pEquipA.GetEmptyHoleNum() == pEquipA.GetHoleNum())
{
message = GetGameUIMan().GetStringFromTable(227);
CECUIManager.Instance.ShowMessageBox(new MessageBoxData()
{
Message = message,
Dlg = this
});
CECUIManager.Instance.ShowMessageBoxGeneral("", message, this);
return;
}
message = GetGameUIMan().GetStringFromTable(229);
var x = new MessageBoxData()
CECUIManager.Instance.ShowMessageBoxYesAndNo("", message, this,
() =>
{
Message = message,
Dlg = this,
MessageBoxType = MessageBoxType.BothYesNoButton,
OnClickedYes = () =>
{
UnityGameSession.c2s_CmdNPCSevClearEmbeddedChip((ushort)m_FirstInvSlot, pIvtrA.GetTemplateID());
ClearEquiment();
pHost.GetPack(InventoryConst.IVTRTYPE_PACK).UnfreezeAllItems();
UnityGameSession.c2s_CmdNPCSevClearEmbeddedChip((ushort)m_FirstInvSlot, pIvtrA.GetTemplateID());
ClearEquiment();
pHost.GetPack(InventoryConst.IVTRTYPE_PACK).UnfreezeAllItems();
string successMessage = GetGameUIMan().GetStringFromTable(230);
CECUIManager.Instance.ShowMessageBox(new MessageBoxData()
{
Message = successMessage,
Dlg = this
});
}
};
CECUIManager.Instance.ShowMessageBox(x);
string successMessage = GetGameUIMan().GetStringFromTable(230);
CECUIManager.Instance.ShowMessageBoxGeneral("", message, this);
}, null);
}
else
{
@@ -592,16 +412,127 @@ namespace BrewMonster
CloseDialogue();
}
private void CheckHidePanel(Vector2 screenPos)
private void OnUseItemClicked()
{
if (!RectTransformUtility.RectangleContainsScreenPoint(
itemInventoryRoot as RectTransform, screenPos,
Camera.main))
itemInventoryRoot.SetActive(false);
if (!TryGetSelectedInventoryItem(out var selectedItem, out var selectedSlot))
return;
if (m_Mode == InstallMode.Disenchase)
{
if (itemInventoryRoot != null)
itemInventoryRoot.gameObject.SetActive(false);
if (!selectedItem.IsEquipment())
{
var message = GetGameUIMan().GetStringFromTable(223);
CECUIManager.Instance.ShowMessageBoxGeneral("", message, this);
return;
}
AssignEquipItem(selectedItem, selectedSlot);
return;
}
if (m_SelectedEquip == null)
{
if (!selectedItem.IsEquipment())
{
var msg = GetGameUIMan().GetStringFromTable(223);
CECUIManager.Instance.ShowMessageBoxGeneral("", msg, this);
return;
}
AssignEquipItem(selectedItem, selectedSlot);
return;
}
if (selectedItem.IsEquipment())
{
AssignEquipItem(selectedItem, selectedSlot);
return;
}
if(!selectedItem.IsEmbeddable() ||
selectedItem.GetClassID() != (int)EC_IvtrEquip.EQUIP_CLASS_ID.ICID_STONE)
{
var msg = GetGameUIMan().GetStringFromTable(225);
CECUIManager.Instance.ShowMessageBoxGeneral("", msg, this);
return;
}
AssginMaterialItem(selectedItem, selectedSlot);
}
private bool TryGetSelectedInventoryItem(out EC_IvtrItem item, out int slot)
{
item = null;
slot = -1;
var inventoryUI = FindFirstObjectByType<EC_InventoryUI>();
if(inventoryUI == null)
return false;
var type = typeof(EC_InventoryUI);
var packageField = type.GetField("currentSelectedPackage", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var slotField = type.GetField("currentSelectedSlot", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var itemField = type.GetField("currentSelectedItem", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (packageField == null ||slotField == null || itemField == null)
{
return false;
}
var selectedPackage = (byte)packageField.GetValue(inventoryUI);
if(selectedPackage != 0)
return false;
slot = (int)slotField.GetValue(inventoryUI);
item = itemField.GetValue(inventoryUI) as EC_IvtrItem;
return item != null && slot >= 0;
}
private void AssignEquipItem(EC_IvtrItem item, int slot)
{
if(m_FirstInvSlot >= 0 && m_FirstInvSlot != slot)
{
ReturnItemToInventory(m_FirstInvSlot);
}
m_SelectedEquip?.Freeze(false);
m_SelectedEquip = item;
m_SelectedEquip.Freeze(true);
m_FirstInvSlot = slot;
m_TxtFirstName.text = EC_IvtrItemUtils.Instance.ResolveItemName(item.m_tid);
SetSlotIcon(m_SlotFirstParent, item);
SetInventorySlotGray(FindInventoryButtonBySlot(slot), true);
if(m_Mode == InstallMode.Disenchase)
{
CalculateUninstallPrice(item);
}
else
{
UpdateResourceInfo();
}
}
private void AssginMaterialItem(EC_IvtrItem item, int slot)
{
if(m_SecondInvSlot >= 0 && m_SecondInvSlot != slot)
{
ReturnItemToInventory(m_SecondInvSlot);
}
m_SelectedMaterial?.Freeze(false);
m_SelectedMaterial = item;
m_SelectedMaterial.Freeze(true);
m_SecondInvSlot = slot;
m_TxtSecondName.text = EC_IvtrItemUtils.Instance.ResolveItemName(item.m_tid);
SetSlotIcon(m_SlotSecondParent, item);
SetInventorySlotGray(FindInventoryButtonBySlot(slot), true);
if (item is EC_IvtrStone stone)
{
var essence = stone.GetDBEssence();
m_install_price = essence.install_price;
}
UpdateResourceInfo();
}
private void UpdateResourceInfo()
@@ -655,4 +586,4 @@ namespace BrewMonster
}
}
}
}
}
@@ -137,7 +137,7 @@ namespace BrewMonster.UI
CDLGNPC_CARDRESPAWN = 0xFFFFF49, // ¿¨ÅÆ×ªÉú
CDLGNPC_QUERYCHARIOTAMOUNT = 0xFFFFF50, // Õ½³µÊýÁ¿
CDLGNPC_FLYSWORDIMPROVE = 0xFFFFF51, // ·É½£Ç¿»¯
CDLGNPC_OPEN_FACTION_PVP = 0xFFFFF52, // ¿ªÆô°ïÅÉÂÓ¶á
CDLGNPC_OPEN_FACTION_PVP = 0xFFFFF52, // ¿ªÆô°ïÅÉÂÓ¶á
CDLGNPC_FACTION_RENAME = 0xFFFFF53,
CDLGNPC_GOLD_SHOP = 0xFFFFF54,
CDLGNPC_PLAYER_CHANGE_GENDER = 0xFFFFF55; // ÐÞ¸ÄÐÔ±ð
@@ -765,10 +765,10 @@ namespace BrewMonster.UI
NPC_MAKE_SERVICE pService = (NPC_MAKE_SERVICE)pData;
string serviceName = Encoding.Unicode.GetString(MemoryMarshal.AsBytes<ushort>(pService.name));
m_pLst_Main.AddString(strText + serviceName);
// Log NPC_MAKE_SERVICE data
BMLogger.Log($"NPC_MAKE_SERVICE detected - ServiceID: {a_uiService[i]}, MakeServiceID: {pService.id}, Name: {serviceName}, MakeSkillID: {pService.id_make_skill}, ProduceType: {pService.produce_type}");
// Log pages data
if (pService.pages != null)
{
@@ -778,7 +778,7 @@ namespace BrewMonster.UI
string pageTitle = Encoding.Unicode.GetString(MemoryMarshal.AsBytes<ushort>(page.page_title));
// Trim null characters and whitespace from page title
pageTitle = pageTitle?.TrimEnd('\0', ' ', '\t', '\r', '\n') ?? "";
// Collect all non-zero goods IDs with their names
// Note: id_goods contains RECIPE IDs, not item IDs
List<string> goodsInfo = new List<string>();
@@ -804,7 +804,7 @@ namespace BrewMonster.UI
// Try recipe space first
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
object recipeData = edm.get_data_ptr(recipeId, ID_SPACE.ID_SPACE_RECIPE, ref dt);
// Check if we got recipe data - sometimes dt is DT_INVALID but data is still RECIPE_ESSENCE
RECIPE_ESSENCE? recipe = null;
if (recipeData != null && recipeData is RECIPE_ESSENCE)
@@ -815,11 +815,11 @@ namespace BrewMonster.UI
{
recipe = (RECIPE_ESSENCE)recipeData;
}
if (recipe.HasValue)
{
RECIPE_ESSENCE recipeValue = recipe.Value;
// Get output item from first target (main output)
if (recipeValue.targets != null && recipeValue.targets.Length > 0)
{
@@ -853,7 +853,7 @@ namespace BrewMonster.UI
{
BMLogger.LogWarning($" Recipe {recipeId}: targets is null or empty");
}
// Get all materials
if (recipeValue.materials != null)
{
@@ -875,7 +875,7 @@ namespace BrewMonster.UI
{
materialName = $"error: {ex2.Message}";
}
string matEntry = !string.IsNullOrEmpty(materialName)
? $"{material.id} ({materialName}) x{material.num}"
: $"{material.id} (unknown) x{material.num}";
@@ -894,7 +894,7 @@ namespace BrewMonster.UI
{
BMLogger.LogWarning($" Failed to get data for recipe ID {recipeId}: {ex.Message}\n{ex.StackTrace}");
}
// Format: "RecipeID -> Output: ID (Name) | Materials: ID (Name) xCount, ..."
string goodsEntry = $"Recipe {recipeId}";
if (!string.IsNullOrEmpty(outputItemInfo))
@@ -917,7 +917,7 @@ namespace BrewMonster.UI
}
}
}
// Log page if it has a title or has goods
if (!string.IsNullOrEmpty(pageTitle) || goodsInfo.Count > 0)
{
@@ -931,22 +931,22 @@ namespace BrewMonster.UI
{
NPC_DECOMPOSE_SERVICE pService = (NPC_DECOMPOSE_SERVICE)pData;
string serviceName = Encoding.Unicode.GetString(MemoryMarshal.AsBytes<ushort>(pService.name));
// Log NPC_DECOMPOSE_SERVICE data
BMLogger.Log($"NPC_DECOMPOSE_SERVICE detected - ServiceID: {a_uiService[i]}, DecomposeServiceID: {pService.id}, Name: {serviceName}, DecomposeSkillID: {pService.id_decompose_skill}");
CECHostPlayer hostPlayer = GetHostPlayer();
bool hasRequiredSkill = false;
// TODO: Implement proper skill check when GetPassiveSkillNum() and GetPassiveSkillByIndex() are available
// For now, we'll use reflection to access private GetPassiveSkillByID method or implement a workaround
// The C++ code checks if player has the required decompose skill (TYPE_LIVE or TYPE_PRODUCE)
try
{
// Try using reflection to access private GetPassiveSkillByID method
var method = typeof(CECHostPlayer).GetMethod("GetPassiveSkillByID",
var method = typeof(CECHostPlayer).GetMethod("GetPassiveSkillByID",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (method != null)
{
var pSkill = method.Invoke(hostPlayer, new object[] { (int)pService.id_decompose_skill, false }) as CECSkill;
@@ -954,9 +954,9 @@ namespace BrewMonster.UI
{
int skillType = pSkill.GetType();
int skillID = pSkill.GetSkillID();
BMLogger.Log($" Found skill by ID (via reflection): ID={skillID}, Type={skillType}");
// Check if this is the required decompose skill with correct type
if ((skillType == (int)CECSkill.SkillType.TYPE_LIVE ||
skillType == (int)CECSkill.SkillType.TYPE_PRODUCE) &&
@@ -999,7 +999,7 @@ namespace BrewMonster.UI
m_pLst_Main.SetItemData(m_pLst_Main.GetCount() - 1, a_uiService[i]);
m_pLst_Main.SetItemDataPtr(m_pLst_Main.GetCount() - 1, pData);
}
if (!hasRequiredSkill)
continue;
}
@@ -1134,7 +1134,7 @@ namespace BrewMonster.UI
// m_pLst_Main.AddString(strText + GetStringFromTable(699));
// m_pLst_Main.SetItemData(m_pLst_Main.GetCount() - 1, CDLGNPC_BATTLECHALLENGE);
// }
// // if( gtime.tm_wday == 5 && gtime.tm_hour >= 18 ||
// // if( gtime.tm_wday == 5 && gtime.tm_hour >= 18 ||
// // gtime.tm_wday == 6 ||
// // gtime.tm_wday == 0 )
// if (GetHostPlayer().GetFRoleID() != GNETRoles._R_UNMEMBER)
@@ -1647,7 +1647,7 @@ namespace BrewMonster.UI
SetDataPtr(pTalk, "ptr_talk_proc");
if (!IsShow()) Show(true);
}
// bool c(int idFunction, int iService, object pData)
// {
// AUIDialog pShow1 = null, pShow2 = null;
@@ -1687,7 +1687,7 @@ namespace BrewMonster.UI
// {
// shopManager.OpenNPCShop(npcID);
// }
// }
// }
// }
// else if (idFunction == (int)SERVICE_TYPE.NPC_INSTALL)
// {
@@ -1994,7 +1994,7 @@ namespace BrewMonster.UI
//
// return true;
// }
public void OnCommand_back(string szCommand)
{
NPC_ESSENCE? pCurNPCEssence = GetGameUIMan().m_pCurNPCEssence;
@@ -2096,7 +2096,7 @@ namespace BrewMonster.UI
if (id != pTalk.windows[i].id) continue;
// TO DO: fix later
// TO DO: show popup with content is talk_text and 1 btn OK
// TO DO: show popup with content is talk_text and 1 btn OK
//GetGameUIMan().MessageBox("", pTask.FormatTaskTalk(pTalk.windows[i].talk_text),
// MB_OK, A3DCOLORRGBA(255, 255, 255, 160));
GetGameUIMan().EndNPCService();
@@ -3236,21 +3236,21 @@ namespace BrewMonster.UI
{
NPC_MAKE_SERVICE pService = (NPC_MAKE_SERVICE)pData;
string serviceName = Encoding.Unicode.GetString(MemoryMarshal.AsBytes<ushort>(pService.name));
// Log NPC_MAKE_SERVICE data when selected
BMLogger.Log($"SelectListItem - NPC_MAKE_SERVICE selected - ServiceID: {iService}, MakeServiceID: {pService.id}, Name: {serviceName}, MakeSkillID: {pService.id_make_skill}, ProduceType: {pService.produce_type}");
idFunction = (int)SERVICE_TYPE.NPC_MAKE;
}
else if (DataType == DATA_TYPE.DT_NPC_DECOMPOSE_SERVICE)
{
NPC_DECOMPOSE_SERVICE pService = (NPC_DECOMPOSE_SERVICE)pData;
string serviceName = Encoding.Unicode.GetString(MemoryMarshal.AsBytes<ushort>(pService.name));
// Log NPC_DECOMPOSE_SERVICE data when selected
BMLogger.Log($"SelectListItem - NPC_DECOMPOSE_SERVICE selected - ServiceID: {iService}, DecomposeServiceID: {pService.id}, Name: {serviceName}, DecomposeSkillID: {pService.id_decompose_skill}");
BMLogger.Log($" Note: This decompose service is being treated as idFunction={SERVICE_TYPE.NPC_DECOMPOSE}");
idFunction = (int)SERVICE_TYPE.NPC_DECOMPOSE;
}
else if (DataType == DATA_TYPE.DT_NPC_IDENTIFY_SERVICE)
@@ -3342,7 +3342,7 @@ namespace BrewMonster.UI
CECTaskInterface pTask;
ad.m_ulCandItems = 0;
opt.param = 0;
// Check if the integer matches any enum value
if (Enum.IsDefined(typeof(SERVICE_TYPE), idFunction))
{
@@ -3391,7 +3391,7 @@ namespace BrewMonster.UI
if (pCurNPCEssence.HasValue)
{
uint npcID = pCurNPCEssence.Value.id;
// var dlgInstall =GetGameUIMan().GetDialog("Win_Enchase");
// dlgInstall.Show(true);
CECUIManager.Instance.ShowUI("Win_Enchase");
@@ -3446,14 +3446,14 @@ namespace BrewMonster.UI
else if (idFunction == (int)SERVICE_TYPE.NPC_MAKE)
{
NPC_MAKE_SERVICE pMake = (NPC_MAKE_SERVICE)pData;
BMLogger.Log($"PopupCorrespondingServiceDialog - NPC_MAKE: produce_type={pMake.produce_type}, MakeSkillID={pMake.id_make_skill}");
// Dialog loading commented out - Win_Produce dialog not yet implemented
// Determine which dialog to use based on produce_type
//string dialogName = (pMake.produce_type == 2) ? "Win_Produce1" : "Win_Produce";
//pShow1 = m_pAUIManager.GetDialog(dialogName);
//if (pShow1 == null)
//{
// BMLogger.LogError($"NPC_MAKE: Dialog '{dialogName}' not found! Service may not work correctly.");
@@ -3464,7 +3464,7 @@ namespace BrewMonster.UI
//{
// // Get or set DlgProduce reference (if it exists)
// // GetGameUIMan().m_pDlgProduce = (CDlgProduce*)pShow1;
//
//
// // Prepare NPC service
// try
// {
@@ -3474,7 +3474,7 @@ namespace BrewMonster.UI
// {
// BMLogger.LogError($"NPC_MAKE: Error calling PrepareNPCService: {ex.Message}");
// }
//
//
// // Set data pointer
// try
// {
@@ -3484,7 +3484,7 @@ namespace BrewMonster.UI
// {
// BMLogger.LogError($"NPC_MAKE: Error setting data pointer: {ex.Message}");
// }
//
//
// // Clear material for certain produce types
// if (pMake.produce_type == 1 ||
// pMake.produce_type == 3 ||
@@ -3502,10 +3502,10 @@ namespace BrewMonster.UI
// BMLogger.LogError($"NPC_MAKE: Error clearing material: {ex.Message}");
// }
// }
//
//
// // Get inventory dialog
// pShow2 = m_pAUIManager.GetDialog("Win_Inventory");
//
//
// // Update produce dialog
// try
// {
@@ -3683,7 +3683,7 @@ namespace BrewMonster.UI
pTask = GetHostPlayer().GetTaskInterface();
pTask.GetAwardCandidates(opt.param, ref ad);
if (ad.m_ulCandItems > 1)
{
dialogue1 = "Win_Award";
@@ -3827,17 +3827,10 @@ namespace BrewMonster.UI
var dlg2 = CECUIManager.Instance.ShowUI(dialogue2);
}
}
CloseDialogue();
return true;
}
public override void CloseDialogue()
{
base.CloseDialogue();
GetGameUIMan().EndNPCService();
}
public void OnCommand_CANCEL(string szCommand)
{
int idCurFinishTask = GetGameUIMan().m_idCurFinishTask;
@@ -3847,6 +3840,7 @@ namespace BrewMonster.UI
GetGameUIMan().m_idCurFinishTask = -1;
}
CloseDialogue();
GetGameUIMan().EndNPCService();
}
public override void Awake()
@@ -78,8 +78,8 @@ namespace BrewMonster.UI
void OnAddFriend(int characterId)
{
Debug.Log("OnAddFriend: " + characterId);
// TODO: c2s add friend when available
string name = EC_ManMessageMono.Instance?.GetECManPlayer?.GetElsePlayer(characterId)?.GetName() ?? "";
UnityGameSession.Friend_Add(characterId, name);
}
void OnDuel(int characterId)
+139 -31
View File
@@ -1,16 +1,19 @@
using System;
using System.Collections.Generic;
using BrewMonster.Network;
using BrewMonster.Managers;
using BrewMonster.Scripts.Task;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Chat;
using CSNetwork.GPDataType;
using CSNetwork;
namespace BrewMonster.Scripts.UI
{
public class CECUIHelper
{
public static string DlgTaskName = "Win_Quest";
public static A3DVECTOR3 GetTaskObjectCoordinates(int id, ref bool in_table)
{
in_table = false;
@@ -24,7 +27,7 @@ namespace BrewMonster.Scripts.UI
GPDataTypeHelper.ISNPCID(id) ||
GPDataTypeHelper.ISMATTERID(id) ||
(id > 100000000); // player ids are typically huge; template ids are usually small
A3DVECTOR3 ret = new A3DVECTOR3(0);
var world = EC_Game.GetGameRun()?.GetWorld();
if (world != null && isLikelyRuntimeObjectId)
@@ -34,17 +37,20 @@ namespace BrewMonster.Scripts.UI
{
var objPos = obj.transform.position;
in_table = true;
BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates isLikelyRuntimeObjectId: objPos={objPos.x},{objPos.y},{objPos.z}, inTable={in_table}");
BMLogger.Log(
$"[CECUIHelper] GetTaskObjectCoordinates isLikelyRuntimeObjectId: objPos={objPos.x},{objPos.y},{objPos.z}, inTable={in_table}");
ret.Set(objPos.x, objPos.y, objPos.z);
return ret;
}
}
List<OBJECT_COORD> TargetTemp = new List<OBJECT_COORD>();
ret = EC_Game.GetGameRun()?.GetHostPlayer().GetObjectCoordinates(id, out TargetTemp, ref in_table) ?? new A3DVECTOR3(0);
ret = EC_Game.GetGameRun()?.GetHostPlayer().GetObjectCoordinates(id, out TargetTemp, ref in_table) ??
new A3DVECTOR3(0);
// 1) Try live NPC/Monster in scene by template id (best match to "click name -> go to that entity")
// 1) 先尝试在场景中按模板ID查找活体NPC/怪物(最符合“点名字就去找它”)
//This only work in Major map not A61. Skip for now.
// if(!in_table)
// {
@@ -65,14 +71,16 @@ namespace BrewMonster.Scripts.UI
// {
// BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates in_table: ret={ret.x},{ret.y},{ret.z}, inTable={in_table}");
// }
if(ret.x != 0 && ret.y != 0 && ret.z != 0)
if (ret.x != 0 && ret.y != 0 && ret.z != 0)
{
BMLogger.Log($"[CECUIHelper] GetHostPlayer GetObjectCoordinates True ret={ret.x},{ret.y},{ret.z}, inTable={in_table}");
BMLogger.Log(
$"[CECUIHelper] GetHostPlayer GetObjectCoordinates True ret={ret.x},{ret.y},{ret.z}, inTable={in_table}");
return ret;
}
else
{
BMLogger.Log($"[CECUIHelper] GetHostPlayer TryGetFirstObjectCoord False ret={ret.x},{ret.y},{ret.z}, inTable={in_table}");
BMLogger.Log(
$"[CECUIHelper] GetHostPlayer TryGetFirstObjectCoord False ret={ret.x},{ret.y},{ret.z}, inTable={in_table}");
}
// Fallback to task_npc table (C++: ATaskTemplMan::GetTaskNPCInfo)
@@ -83,10 +91,12 @@ namespace BrewMonster.Scripts.UI
// NOTE: Keep original PW coordinate mapping: ret.Set(x, z, y)
// 注意:保持原版坐标映射:ret.Set(x, z, y)
in_table = true;
BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates TryGetTaskNPCInfo: info.x={info.x}, info.z={info.z}, info.y={info.y}, inTable={in_table}");
BMLogger.Log(
$"[CECUIHelper] GetTaskObjectCoordinates TryGetTaskNPCInfo: info.x={info.x}, info.z={info.z}, info.y={info.y}, inTable={in_table}");
return new A3DVECTOR3(info.x, info.z, info.y);
}
else{
else
{
BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates TryGetTaskNPCInfo: not found for id={id}");
}
@@ -95,14 +105,17 @@ namespace BrewMonster.Scripts.UI
if (BrewMonster.Network.EC_Game.TryGetFirstObjectCoord(id.ToString(), out var coordPos, out var mapName))
{
in_table = true;
BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates TryGetFirstObjectCoord: coordPos.x={coordPos.x}, coordPos.y={coordPos.y}, coordPos.z={coordPos.z}, inTable={in_table}");
BMLogger.Log(
$"[CECUIHelper] GetTaskObjectCoordinates TryGetFirstObjectCoord: coordPos.x={coordPos.x}, coordPos.y={coordPos.y}, coordPos.z={coordPos.z}, inTable={in_table}");
return new A3DVECTOR3(coordPos.x, coordPos.y, coordPos.z);
}
UnityEngine.Debug.LogWarning($"[CECUIHelper] GetTaskObjectCoordinates: Not found for id={id} (isLikelyRuntimeObjectId={isLikelyRuntimeObjectId}).");
BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates default return ret={ret.x},{ret.y},{ret.z}, inTable={in_table}");
UnityEngine.Debug.LogWarning(
$"[CECUIHelper] GetTaskObjectCoordinates: Not found for id={id} (isLikelyRuntimeObjectId={isLikelyRuntimeObjectId}).");
BMLogger.Log(
$"[CECUIHelper] GetTaskObjectCoordinates default return ret={ret.x},{ret.y},{ret.z}, inTable={in_table}");
return ret;
// TODO: Implement this method properly
// A3DVECTOR3 ret(0.f);
// in_table = false;
@@ -119,7 +132,7 @@ namespace BrewMonster.Scripts.UI
// }
// return ret;
}
// Follow coord like C++ CECUIHelper::FollowCoord(enumEICoord, taskId)
// 像C++的CECUIHelper::FollowCoord(enumEICoord, taskId)一样跟随坐标
public static bool FollowCoord(int id, int taskId)
@@ -163,9 +176,12 @@ namespace BrewMonster.Scripts.UI
if (shouldForceNavigate)
{
UnityEngine.Debug.Log($"[CECUIHelper] FollowCoord: taskId={taskId} => force navigate (bezier) instead of normal auto-move");
hostPlayer.OnNaviageEvent(taskId, (int)BrewMonster.Scripts.CECNavigateCtrl.NavigateEvent.EM_PREPARE);
hostPlayer.OnNaviageEvent(taskId, (int)BrewMonster.Scripts.CECNavigateCtrl.NavigateEvent.EM_BEGIN);
UnityEngine.Debug.Log(
$"[CECUIHelper] FollowCoord: taskId={taskId} => force navigate (bezier) instead of normal auto-move");
hostPlayer.OnNaviageEvent(taskId,
(int)BrewMonster.Scripts.CECNavigateCtrl.NavigateEvent.EM_PREPARE);
hostPlayer.OnNaviageEvent(taskId,
(int)BrewMonster.Scripts.CECNavigateCtrl.NavigateEvent.EM_BEGIN);
return true;
}
}
@@ -189,7 +205,8 @@ namespace BrewMonster.Scripts.UI
if (templ != null)
{
// Helper local function: pick first region center if in current world
bool TryUseRegion(uint worldId, uint cnt, BrewMonster.Scripts.Task.Task_Region[] regions, string tag, out A3DVECTOR3 pos)
bool TryUseRegion(uint worldId, uint cnt, BrewMonster.Scripts.Task.Task_Region[] regions,
string tag, out A3DVECTOR3 pos)
{
pos = new A3DVECTOR3(0);
if (cnt == 0 || regions == null || regions.Length == 0) return false;
@@ -205,21 +222,27 @@ namespace BrewMonster.Scripts.UI
// 1) Deliver zone (often where quest giver is)
if (templ.m_FixedData.m_bDelvInZone &&
TryUseRegion(templ.m_FixedData.m_ulDelvWorld, templ.m_FixedData.m_ulDelvRegionCnt, templ.m_FixedData.m_pDelvRegion, "DelvInZone", out vPos))
TryUseRegion(templ.m_FixedData.m_ulDelvWorld, templ.m_FixedData.m_ulDelvRegionCnt,
templ.m_FixedData.m_pDelvRegion, "DelvInZone", out vPos))
{
inTable = true;
}
// 2) Reach-site regions (for reach-site tasks / also can be used as guidance)
else if (TryUseRegion(templ.m_FixedData.m_ulReachSiteId, templ.m_FixedData.m_ulReachSiteCnt, templ.m_FixedData.m_pReachSite, "ReachSite", out vPos))
else if (TryUseRegion(templ.m_FixedData.m_ulReachSiteId, templ.m_FixedData.m_ulReachSiteCnt,
templ.m_FixedData.m_pReachSite, "ReachSite", out vPos))
{
inTable = true;
}
// 3) Enter region / leave region zones (some tasks use these to define where objectives happen)
else if (TryUseRegion(templ.m_FixedData.m_ulEnterRegionWorld, templ.m_FixedData.m_ulEnterRegionCnt, templ.m_FixedData.m_pEnterRegion, "EnterRegion", out vPos))
else if (TryUseRegion(templ.m_FixedData.m_ulEnterRegionWorld,
templ.m_FixedData.m_ulEnterRegionCnt, templ.m_FixedData.m_pEnterRegion,
"EnterRegion", out vPos))
{
inTable = true;
}
else if (TryUseRegion(templ.m_FixedData.m_ulLeaveRegionWorld, templ.m_FixedData.m_ulLeaveRegionCnt, templ.m_FixedData.m_pLeaveRegion, "LeaveRegion", out vPos))
else if (TryUseRegion(templ.m_FixedData.m_ulLeaveRegionWorld,
templ.m_FixedData.m_ulLeaveRegionCnt, templ.m_FixedData.m_pLeaveRegion,
"LeaveRegion", out vPos))
{
inTable = true;
}
@@ -228,7 +251,8 @@ namespace BrewMonster.Scripts.UI
if (!inTable)
{
UnityEngine.Debug.LogWarning($"[CECUIHelper] FollowCoord: No coordinates for id={id}, taskId={taskId} (will not move)");
UnityEngine.Debug.LogWarning(
$"[CECUIHelper] FollowCoord: No coordinates for id={id}, taskId={taskId} (will not move)");
return false;
}
}
@@ -266,6 +290,7 @@ namespace BrewMonster.Scripts.UI
{
work.SetTaskNPCInfo(id, taskId);
}
wm.StartWork_p2(work);
return true;
@@ -279,7 +304,8 @@ namespace BrewMonster.Scripts.UI
// 验证输入
if (m_TargetCoord == null || m_TargetCoord.Count == 0)
{
UnityEngine.Debug.LogWarning($"[CECUIHelper] FollowCoord: m_TargetCoord is null or empty, traceName={m_strTraceName} (will not move)");
UnityEngine.Debug.LogWarning(
$"[CECUIHelper] FollowCoord: m_TargetCoord is null or empty, traceName={m_strTraceName} (will not move)");
return false;
}
@@ -292,7 +318,8 @@ namespace BrewMonster.Scripts.UI
// 如果可用,记录地图信息(用于调试)
if (!string.IsNullOrEmpty(targetCoord.strMap))
{
UnityEngine.Debug.Log($"[CECUIHelper] FollowCoord: Target map='{targetCoord.strMap}', traceName={m_strTraceName}");
UnityEngine.Debug.Log(
$"[CECUIHelper] FollowCoord: Target map='{targetCoord.strMap}', traceName={m_strTraceName}");
}
// Start auto-move work to destination (this is what actually moves the player in this project)
@@ -300,7 +327,8 @@ namespace BrewMonster.Scripts.UI
CECHostPlayer host = EC_Game.GetGameRun()?.GetHostPlayer();
if (host == null)
{
UnityEngine.Debug.LogError($"[CECUIHelper] FollowCoord: Host player is null, traceName={m_strTraceName}");
UnityEngine.Debug.LogError(
$"[CECUIHelper] FollowCoord: Host player is null, traceName={m_strTraceName}");
return false;
}
@@ -314,7 +342,8 @@ namespace BrewMonster.Scripts.UI
CECHPWorkMove work = wm.CreateWork(CECHPWork.Host_work_ID.WORK_MOVETOPOS) as CECHPWorkMove;
if (work == null)
{
UnityEngine.Debug.LogError($"[CECUIHelper] FollowCoord: Failed to create WORK_MOVETOPOS, traceName={m_strTraceName}");
UnityEngine.Debug.LogError(
$"[CECUIHelper] FollowCoord: Failed to create WORK_MOVETOPOS, traceName={m_strTraceName}");
return false;
}
@@ -329,13 +358,15 @@ namespace BrewMonster.Scripts.UI
wm.StartWork_p2(work);
UnityEngine.Debug.Log($"[CECUIHelper] FollowCoord: Started auto-move to ({vPos.x},{vPos.y},{vPos.z}) map={targetCoord.strMap}, traceName={m_strTraceName}, coordCount={m_TargetCoord.Count}");
UnityEngine.Debug.Log(
$"[CECUIHelper] FollowCoord: Started auto-move to ({vPos.x},{vPos.y},{vPos.z}) map={targetCoord.strMap}, traceName={m_strTraceName}, coordCount={m_TargetCoord.Count}");
return true;
}
public static void AutoMoveStartComplex(A3DVECTOR3 dst, int targetId = 0, int taskId = 0)
{
UnityEngine.Debug.Log($"[CECUIHelper] AutoMoveStartComplex: dst={dst}, targetId={targetId}, taskId={taskId}");
UnityEngine.Debug.Log(
$"[CECUIHelper] AutoMoveStartComplex: dst={dst}, targetId={targetId}, taskId={taskId}");
// TODO: Implement this method properly
// if( CECAutoPolicy.Instance.IsAutoPolicyEnabled() )
// return;
@@ -348,5 +379,82 @@ namespace BrewMonster.Scripts.UI
msg.dwParam4 = new MsgDataAutoMove(0, targetId, taskId);
EC_ManMessage.PostMessage(0, 0, 0, msg);
}
public static string PolicySpecialCharReplace(
string szText,
CHAT_S2C.PolicyChatParameter pPolicyChatPara)
{
if (string.IsNullOrEmpty(szText))
return szText;
string result = szText;
//result = ReplaceNameInPolicyChat(result, pPolicyChatPara);
string subString;
string key;
string variable;
while (FindSpecialCharInPolicyChat(result, out subString, out key, out variable))
{
/*result = ReplaceSpecialCharInPolicyChat(
result,
subString,
key,
variable,
pPolicyChatPara);*/
}
return result;
}
public static bool FindSpecialCharInPolicyChat(
string srcPolicyChat,
out string subString,
out string keyInSubString,
out string variableInSubString)
{
subString = null;
keyInSubString = null;
variableInSubString = null;
if (string.IsNullOrEmpty(srcPolicyChat))
return false;
int posStart = srcPolicyChat.IndexOf("{");
if (posStart == -1)
return false;
int posEnd = srcPolicyChat.IndexOf("}", posStart);
if (posEnd == -1)
return false;
subString = srcPolicyChat.Substring(posStart, posEnd - posStart + 1);
// remove { }
string inner = subString.Substring(1, subString.Length - 2);
var parts = inner.Split(':');
if (parts.Length == 2)
{
keyInSubString = parts[0];
variableInSubString = parts[1];
return true;
}
return false;
}
public static void RemoveNameFlagFromNPCChat(string chat, out string conv)
{
if (string.IsNullOrEmpty(chat))
{
conv = string.Empty;
return;
}
conv = chat.Replace("&", "");
}
}
}
@@ -391,9 +391,16 @@ namespace BrewMonster.UI
public void ShowErrorMsg(string pszMsg, string pszName)
{
CECUIManager.Instance.ShowMessageBox(pszName, pszMsg, MessageBoxType.YesButton);
CECUIManager.Instance.ShowMessageBoxYes(pszName, pszMsg, null,null);
}
/*CDlgPopMsg m_pDlgPopMsg;
void AddHeartBeatHint(string pszMsg)
{
m_pDlgPopMsg->Add(pszMsg);
}*/
}
public enum EC_GAMEUI_ICONS : byte
{
ICONS_ACTION = 0,
@@ -55,11 +55,7 @@ namespace BrewMonster
player.GetCurSkill() != null ||
player.IsFighting())
{
uiManager.ShowMessageBox(new MessageBoxData()
{
Title = "MessageBox",
Message = gameUIMan.GetStringFromTable(11327)
});
uiManager.ShowMessageBoxGeneral("MessageBox", gameUIMan.GetStringFromTable(11327), this);
}
int nCondition = CECHostSkillModel.Instance.CheckLearnCondition(m_skillID);
@@ -102,12 +98,14 @@ namespace BrewMonster
int needSp = CECHostSkillModel.Instance.GetSkillSp(m_skillID, m_curLevel + 1);
string str = GPDataTypeHelper.ReplacePercentD(GetStringFromTable(11326), needMoney, needSp);
var messagebox = uiManager.ShowMessageBox(new MessageBoxData()
{
Title = "Game_LearnSkill",
Message = str,
OnClickedYes = () => UnityGameSession.c2s_SendCmdNPCSevLearnSkill(m_skillID)
});
// var messagebox = uiManager.ShowMessageBox(new MessageBoxData()
// {
// Title = "Game_LearnSkill",
// Message = str,
// OnClickedYes = () => UnityGameSession.c2s_SendCmdNPCSevLearnSkill(m_skillID)
// });
var messagebox = uiManager.ShowMessageBoxYes("Game_LearnSkill", str, this,
() => UnityGameSession.c2s_SendCmdNPCSevLearnSkill(m_skillID));
messagebox.SetData((uint)m_skillID);
//GetGameUIMan()->MessageBox("Game_LearnSkill", str, //GetGameUIMan()->GetStringFromTable(231),
// MB_OKCANCEL, A3DCOLORRGBA(255, 255, 255, 160), &pMsgBox);
@@ -40,12 +40,8 @@ namespace BrewMonster.UI
void OnCommandRepick()
{
CECUIManager.Instance.ShowMessageBox(
title: "Thoát",
message: CECUIManager.Instance.GetInGameUIMan().GetStringFromTable(202),
messageBoxType: MessageBoxType.BothYesNoButton,
onClickedYes: OnClickYes
);
CECUIManager.Instance.ShowMessageBoxYes("Thoát",
CECUIManager.Instance.GetInGameUIMan().GetStringFromTable(202), null, OnClickYes);
}
void OnClickYes()
@@ -14,7 +14,7 @@ using UnityEngine.UI;
namespace BrewMonster.UI
{
/// <summary>
/// Login Flow:
/// Login Flow:
/// 1. Enter username and password
/// 2. Click login button
/// 3. Login success, get the list of characters
@@ -92,13 +92,14 @@ namespace BrewMonster.UI
#if UNITY_EDITOR
if (Input.GetKeyUp(KeyCode.LeftAlt))
{
_usernameInputField.text = "test004";
_usernameInputField.text = "test016";
_passwordInputField.text = "123456";
OnLoginButtonClicked();
}
if (Input.GetKeyUp(KeyCode.Tab))
{
_usernameInputField.text = "test002";
_usernameInputField.text = "test017";
_passwordInputField.text = "123456";
OnLoginButtonClicked();
}
@@ -135,7 +136,7 @@ namespace BrewMonster.UI
BMLogger.LogError("[LoginScreenUI] Username/password empty.");
await BeginGameLoginAsync(_usernameInputField.text, _passwordInputField.text);
}
}
private async Task BeginGameLoginAsync(string username, string password)
@@ -179,7 +180,7 @@ namespace BrewMonster.UI
// If we're returning to select role, skip straight to select role without showing login UI again, since we never fully left the game session.
OnLoginComplete(true);
return;
// Auto-login to reach Select Role like the original client, without showing Tech3C auth UI again.
if (!string.IsNullOrEmpty(_usernameInputField.text) && !string.IsNullOrEmpty(_passwordInputField.text))
{
@@ -192,7 +193,7 @@ namespace BrewMonster.UI
}
/// <summary>
/// Callback when the login is complete.
/// Callback when the login is complete.
/// Then get the list of characters
/// </summary>
private void OnLoginComplete(bool result)
@@ -214,7 +215,7 @@ namespace BrewMonster.UI
}
/// <summary>
/// Callback when the list of characters is retrieved.
/// Callback when the list of characters is retrieved.
/// Then move to the select character screen
/// </summary>
private void OnGetRoleListComplete(List<RoleInfo> roleInfos)
@@ -344,7 +345,7 @@ namespace BrewMonster.UI
UnityGameSession.EnterWorldAsync(roleInfo, OnEnterWorldComplete);
#else
string nameScene = UnityGameSession.Instance.GetWorldInstanceName();
UnityGameSession.Instance.LoadScene(nameScene, LoadSceneMode.Single,
UnityGameSession.Instance.LoadScene(nameScene, LoadSceneMode.Single,
(progress) =>
{
LoadingSceneController.Instance.SetProgress(progress);
@@ -356,7 +357,7 @@ namespace BrewMonster.UI
isDoneWorldRender = true;
actLoadChar?.Invoke();
UnityGameSession.EnterWorldAsync(roleInfo, OnEnterWorldComplete);
});
#endif
}, null);
@@ -369,7 +370,9 @@ namespace BrewMonster.UI
UnityGameSession.RequestAllInventoriesAsync(() => { /*BMLogger.Log("Sent Inventory Detail Requests (all packs)");*/ }, 0, 1, 2);
await Task.Delay(1000);
UnityGameSession.RequestCheckSecurityPassWd("");
// C++ friend_GetList(); required before Add Friend
await Task.Delay(1000);
UnityGameSession.Friend_GetList();
}
//private void OnInventoryReceived(List<InventoryItem> inventoryData)
@@ -433,4 +436,4 @@ namespace BrewMonster.UI
}
}
}
}
}
+71 -1
View File
@@ -1,5 +1,8 @@
using System;
using System.Threading;
using BrewMonster.Scripts.ChatUI;
using CSNetwork.GPDataType;
using Cysharp.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -12,9 +15,14 @@ namespace BrewMonster.PerfectWorld.Scripts.UI
public Image healthImage;
[SerializeField] private TextMeshProUGUI nameText;
[SerializeField] private Transform canvasRoot;
[SerializeField] private TextMeshProUGUI chatText;
[Header("References")]
[SerializeField] private CECPlayer hostplayer;
[SerializeField] private float chatDisplayDuration = 5f;
private CancellationTokenSource _chatCts;
private const string DefaultLayerName = "Default";
private bool _isVisible;
@@ -34,18 +42,22 @@ namespace BrewMonster.PerfectWorld.Scripts.UI
return;
}
SetVisible(false);
SetVisible(true);
RefreshName();
EventBus.SubscribeChannel<EventChatMessageOnTopPlayer>(hostplayer.GetCharacterID(), SetChatMessage);
EventBus.SubscribeChannel<cmd_self_info_00>(hostplayer.m_PlayerInfo.cid, UpdateHostPlayerInfoUI);
}
private void OnDestroy()
{
_chatCts?.Cancel();
_chatCts?.Dispose();
if (hostplayer == null)
{
return;
}
EventBus.UnsubscribeChannel<EventChatMessageOnTopPlayer>(hostplayer.GetCharacterID(), SetChatMessage);
EventBus.UnsubscribeChannel<cmd_self_info_00>(hostplayer.m_PlayerInfo.cid, UpdateHostPlayerInfoUI);
}
@@ -121,5 +133,63 @@ namespace BrewMonster.PerfectWorld.Scripts.UI
for (int i = 0; i < t.childCount; i++)
SetLayerRecursively(t.GetChild(i).gameObject, layer);
}
private void SetChatMessage(EventChatMessageOnTopPlayer cxt)
{
if (chatText == null)
{
BMLogger.LogError("Don't have chatText TMProUI");
return;
}
ChatThreadDispatcher.Instance.Post(() =>{
SetLastSaidWords(cxt.context);
});
}
public void SetLastSaidWords(string message, float duration = -1f)
{
if (chatText == null || string.IsNullOrEmpty(message))
return;
chatText.text = message;
chatText.gameObject.SetActive(true);
// cancel timer cũ
_chatCts?.Cancel();
_chatCts = new CancellationTokenSource();
float time = duration > 0 ? duration : chatDisplayDuration;
HideChatAsync(time, _chatCts.Token).Forget();
}
private async UniTaskVoid HideChatAsync(float time, CancellationToken token)
{
try
{
await UniTask.Delay(
TimeSpan.FromSeconds(time),
cancellationToken: token
);
if (chatText != null)
chatText.gameObject.SetActive(false);
}
catch (OperationCanceledException)
{
// chat mới đến → timer cũ bị cancel
}
}
}
public struct EventChatMessageOnTopPlayer
{
public int roleId;
public string context;
public EventChatMessageOnTopPlayer(int roleId, string context)
{
this.roleId = roleId;
this.context = context;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 45d0006e3eb113541bab860d9be89d94
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+117
View File
@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: b2f1cd3ee517556479672deef3004b2d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 38ebca484ba26f2408bed39f0b6b33dd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,46 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1735791580658873054
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1508221814777778566}
- component: {fileID: 5062616109990871891}
m_Layer: 0
m_Name: preFfab_ChatThreadDispatcher
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1508221814777778566
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1735791580658873054}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5062616109990871891
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1735791580658873054}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 00392315a7ee47e7a9527eda504fb312, type: 3}
m_Name:
m_EditorClassIdentifier:
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 820475c3b63a5de4b9b311fe9e0de9b5
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9dee979d4a90a8640b3dd731816b2656
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,228 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6240941777052618231
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5390685607869309037}
- component: {fileID: 7228077960814023056}
- component: {fileID: 5305392080666511277}
m_Layer: 5
m_Name: Text (TMP)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &5390685607869309037
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6240941777052618231}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1869019404724936087}
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 &7228077960814023056
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6240941777052618231}
m_CullTransparentMesh: 1
--- !u!114 &5305392080666511277
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6240941777052618231}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: New Text
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 36
m_fontSizeBase: 36
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 1
m_VerticalAlignment: 8192
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &6627717456258223658
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1869019404724936087}
- component: {fileID: 616079771158270572}
- component: {fileID: 3486315639058223012}
- component: {fileID: 1976417251556044024}
m_Layer: 5
m_Name: prefab_TextContents
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1869019404724936087
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6627717456258223658}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 5390685607869309037}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 800, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &616079771158270572
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6627717456258223658}
m_CullTransparentMesh: 1
--- !u!114 &3486315639058223012
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6627717456258223658}
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: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 0
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!114 &1976417251556044024
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6627717456258223658}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 179d32c667fc2f641bdcb7afb18046b9, type: 3}
m_Name:
m_EditorClassIdentifier:
messageText: {fileID: 5305392080666511277}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: dcc75569599675f46a99bc66a87efc9a
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+124 -16
View File
@@ -10122,11 +10122,12 @@ MonoBehaviour:
m_SlotSecondParent: {fileID: 7151360590639773519}
m_TxtSecondName: {fileID: 4492084240745408081}
m_TxtMoney: {fileID: 6140428454487430115}
m_useItem: {fileID: 29649554038592406}
m_BtnMergeOrReset: {fileID: 8208092408021918524}
m_BtnCancel: {fileID: 4503836757578509720}
m_BtnClose: {fileID: 5942200196902544367}
khung_item: {fileID: 21300000, guid: a5366f3bce011c046902e39b6bd3a077, type: 3}
itemInventoryRoot: {fileID: 6829484673054423729}
itemInventoryRoot: {fileID: 3361511320564075180}
--- !u!1 &5641506892578507279
GameObject:
m_ObjectHideFlags: 0
@@ -11541,12 +11542,12 @@ MonoBehaviour:
- {fileID: 682304874874096685}
equipmentPackButtons: []
fashionPackButtons: []
detailPanelRoot: {fileID: 4012993487235845803}
detailPanelRoot: {fileID: 895914416731758390}
detailPanelOffset: {x: 20, y: 0}
hideDetailOnStart: 1
descriptionText:
legacy: {fileID: 0}
tmp: {fileID: 7977462308482374098}
tmp: {fileID: 6154813818007210063}
equipButton: {fileID: 0}
dropButton: {fileID: 0}
autoRefresh: 1
@@ -15326,7 +15327,7 @@ RectTransform:
- {fileID: 368043242273701515}
- {fileID: 9177880079034759179}
- {fileID: 7246818111234384827}
- {fileID: 6829484673054423729}
- {fileID: 7284815503472110380}
m_Father: {fileID: 2126663214709926210}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
@@ -15494,7 +15495,7 @@ MonoBehaviour:
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1001 &5454539435686182200
--- !u!1001 &8101275948331805861
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
@@ -15502,6 +15503,42 @@ PrefabInstance:
serializedVersion: 3
m_TransformParent: {fileID: 7802877985602690998}
m_Modifications:
- target: {fileID: 636299721907915661, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchorMax.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 636299721907915661, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchorMin.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 636299721907915661, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchoredPosition.x
value: 20
objectReference: {fileID: 0}
- target: {fileID: 636299721907915661, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchoredPosition.y
value: -0
objectReference: {fileID: 0}
- target: {fileID: 777847736648841921, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_text
value: "D\xF9ng"
objectReference: {fileID: 0}
- target: {fileID: 777847736648841921, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_fontSize
value: 48.2
objectReference: {fileID: 0}
- target: {fileID: 777847736648841921, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_fontSizeBase
value: 48.2
objectReference: {fileID: 0}
- target: {fileID: 777847736648841921, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_enableAutoSizing
value: 0
objectReference: {fileID: 0}
- target: {fileID: 777847736648841921, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_VerticalAlignment
value: 256
objectReference: {fileID: 0}
- target: {fileID: 1546246053547542409, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_Pivot.x
value: 0.5
@@ -15532,7 +15569,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 1546246053547542409, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_SizeDelta.y
value: 0
value: 948.02
objectReference: {fileID: 0}
- target: {fileID: 1546246053547542409, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_LocalPosition.x
@@ -15582,37 +15619,103 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1900527214026617767, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_SizeDelta.x
value: 200
objectReference: {fileID: 0}
- target: {fileID: 1900527214026617767, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_SizeDelta.y
value: 67.9
objectReference: {fileID: 0}
- target: {fileID: 1900527214026617767, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchoredPosition.x
value: 532
objectReference: {fileID: 0}
- target: {fileID: 1900527214026617767, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchoredPosition.y
value: -37
objectReference: {fileID: 0}
- target: {fileID: 6830833846243993097, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_Name
value: item_info
objectReference: {fileID: 0}
- target: {fileID: 6830833846243993097, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7209086543831860202, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchorMax.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 7209086543831860202, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchorMin.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 7209086543831860202, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchoredPosition.x
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7209086543831860202, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchoredPosition.y
value: -928.02
objectReference: {fileID: 0}
- target: {fileID: 8894405194986632892, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchorMax.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 8894405194986632892, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchorMin.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 8894405194986632892, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_SizeDelta.y
value: 928.02
objectReference: {fileID: 0}
- target: {fileID: 8894405194986632892, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchoredPosition.x
value: 20
objectReference: {fileID: 0}
- target: {fileID: 8894405194986632892, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
propertyPath: m_AnchoredPosition.y
value: -464.01
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects:
- {fileID: 5721094068644211543, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
- {fileID: 2412057975732520665, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
--- !u!114 &4012993487235845803 stripped
--- !u!114 &29649554038592406 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 8936108025019184019, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
m_PrefabInstance: {fileID: 5454539435686182200}
m_CorrespondingSourceObject: {fileID: 8071811253980610355, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
m_PrefabInstance: {fileID: 8101275948331805861}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &895914416731758390 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 8936108025019184019, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
m_PrefabInstance: {fileID: 8101275948331805861}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3361511320564075180}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fc26b8fa93aea49b4abb8fe5455e51fe, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!224 &6829484673054423729 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 1546246053547542409, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
m_PrefabInstance: {fileID: 5454539435686182200}
--- !u!1 &3361511320564075180 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 6830833846243993097, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
m_PrefabInstance: {fileID: 8101275948331805861}
m_PrefabAsset: {fileID: 0}
--- !u!114 &7977462308482374098 stripped
--- !u!114 &6154813818007210063 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 2668322321768899818, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
m_PrefabInstance: {fileID: 5454539435686182200}
m_PrefabInstance: {fileID: 8101275948331805861}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
@@ -15620,3 +15723,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!224 &7284815503472110380 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 1546246053547542409, guid: c56ed80641ff74ce49f91401e3eb8367, type: 3}
m_PrefabInstance: {fileID: 8101275948331805861}
m_PrefabAsset: {fileID: 0}

Some files were not shown because too many files have changed in this diff Show More