diff --git a/Assets/PerfectWorld/Resources/UI/Login/载入1280-15.jpg b/Assets/PerfectWorld/Resources/UI/Login/载入1280-15.jpg new file mode 100644 index 0000000000..637d40be2d Binary files /dev/null and b/Assets/PerfectWorld/Resources/UI/Login/载入1280-15.jpg differ diff --git a/Assets/PerfectWorld/Resources/UI/Login/载入1280-15.jpg.meta b/Assets/PerfectWorld/Resources/UI/Login/载入1280-15.jpg.meta new file mode 100644 index 0000000000..ecf32c16f9 --- /dev/null +++ b/Assets/PerfectWorld/Resources/UI/Login/载入1280-15.jpg.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: cfa464036967d9746abcfa0608211b2e +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: 0 + 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 + - serializedVersion: 4 + buildTarget: Android + 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: diff --git a/Assets/PerfectWorld/Scene/LoginScene.unity b/Assets/PerfectWorld/Scene/LoginScene.unity index 96d09f5880..ffb3f922c7 100644 --- a/Assets/PerfectWorld/Scene/LoginScene.unity +++ b/Assets/PerfectWorld/Scene/LoginScene.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3553c5fdd68230c6a591df1fde22cf360896fc818007787595189f0cc036b5c -size 69312 +oid sha256:eadad766c90d9a2f8f33468f1b6b21e6fd78e55f555200887eb9f6198720424a +size 76688 diff --git a/Assets/PerfectWorld/Scripts/Common/DataProcess/generate_item_temp.cs b/Assets/PerfectWorld/Scripts/Common/DataProcess/generate_item_temp.cs index 26c91dfd33..48dc7b3a76 100644 --- a/Assets/PerfectWorld/Scripts/Common/DataProcess/generate_item_temp.cs +++ b/Assets/PerfectWorld/Scripts/Common/DataProcess/generate_item_temp.cs @@ -919,6 +919,53 @@ public static class generate_item_temp itemdataman.set_to_classid(DATA_TYPE.DT_FASHION_ESSENCE, data, -1); return 0; } + public static int generate_taskdice(uint id, ID_SPACE idspace, out byte[] data, out uint size, RAND_CLASS cls) + { + DATA_TYPE datatype = DATA_TYPE.DT_INVALID; + data = new byte[0]; + size = 0; + object obj = itemdataman._edm.get_data_ptr(id, idspace, ref datatype); + if(obj == null || datatype != DATA_TYPE.DT_TASKDICE_ESSENCE) + { + return -1; + } + TASKDICE_ESSENCE ess = (TASKDICE_ESSENCE)obj; + size = (uint)(Marshal.SizeOf(typeof(item_data)) + Marshal.SizeOf(typeof(TASKDICE_ESSENCE))); + data = new byte[size]; + int offset = 0; + WriteUInt(data, ref offset, id); + WriteUInt(data, ref offset, 1); + WriteInt(data, ref offset, ess.pile_num_max); + WriteInt(data, ref offset, 0); + WriteUInt(data, ref offset, ess.proc_type); + WriteInt(data, ref offset, (int)DATA_TYPE.DT_TASKDICE_ESSENCE); + if(ess.has_guid == 1) + { + int g1,g2; + itemdataman.get_item_guid(id,out g1,out g2); + WriteInt(data, ref offset, g1); + WriteInt(data, ref offset, g2); + } + else + { + WriteInt(data, ref offset, 0); + WriteInt(data, ref offset, 0); + } + WriteInt(data, ref offset, 0); + WriteInt(data, ref offset, 0); + + int content_length = 0; + int content_length_ptr = offset; + WriteInt(data, ref offset, 0); + int item_content = offset; + WriteInt(data, ref offset, 0); + content_length = (int)(size - offset); + WriteInt(data, ref content_length_ptr, content_length); + WriteInt(data, ref item_content, offset); + + itemdataman.set_to_classid(DATA_TYPE.DT_TASKDICE_ESSENCE, data, -1); + return 0; + } public static int generate_tasknormalmatter(uint id, ID_SPACE idspace, out byte[] data, out uint size, RAND_CLASS cls, GEN_ADDON_MODE normal_addon ,item_tag_t tag,List sa_list = null) { diff --git a/Assets/PerfectWorld/Scripts/Common/DataProcess/itemdataman.cs b/Assets/PerfectWorld/Scripts/Common/DataProcess/itemdataman.cs index 6b96ec7f37..f484a59ba7 100644 --- a/Assets/PerfectWorld/Scripts/Common/DataProcess/itemdataman.cs +++ b/Assets/PerfectWorld/Scripts/Common/DataProcess/itemdataman.cs @@ -469,6 +469,9 @@ namespace BrewMonster case DATA_TYPE.DT_FASHION_ESSENCE: ret = generate_item_temp.generate_fashion_item(id, ID_SPACE.ID_SPACE_ESSENCE, out item, out size, SPECIFIC.SPECIFIC_RAND, tag); break; + case DATA_TYPE.DT_TASKDICE_ESSENCE: + ret = generate_item_temp.generate_taskdice(id, ID_SPACE.ID_SPACE_ESSENCE, out item, out size, SPECIFIC.SPECIFIC_RAND); + break; case DATA_TYPE.DT_TASKNORMALMATTER_ESSENCE: ret = generate_item_temp.generate_tasknormalmatter(id,ID_SPACE.ID_SPACE_ESSENCE, out item,out size,SPECIFIC.SPECIFIC_RAND,GEN_ADDON_MODE.ADDON_LIST_SHOP,tag); diff --git a/Assets/PerfectWorld/Scripts/GameData/ExpTypes.cs b/Assets/PerfectWorld/Scripts/GameData/ExpTypes.cs index 945c631921..4bd4f6926a 100644 --- a/Assets/PerfectWorld/Scripts/GameData/ExpTypes.cs +++ b/Assets/PerfectWorld/Scripts/GameData/ExpTypes.cs @@ -1508,16 +1508,16 @@ namespace BrewMonster [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public byte[] file_model; // Model file path - + public string FileModel => ByteToStringUtils.ByteArrayToCP936String(file_model); [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public byte[] file_model2; // Second model file path [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public byte[] file_matter; // Material file path - + public string FileMatter => ByteToStringUtils.ByteArrayToCP936String(file_matter); [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public byte[] file_icon; // Icon file path - + public string FileIcon => ByteToStringUtils.ByteArrayToCP936String(file_icon); public int price; // Price public int shop_price; // Shop price @@ -1785,9 +1785,10 @@ namespace BrewMonster [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public byte[] file_matter; // matter file path - + public string FileMatter { get { return ByteToStringUtils.ByteArrayToCP936String(file_matter); } } [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public byte[] file_icon; // icon file path + public string FileIcon { get { return ByteToStringUtils.ByteArrayToCP936String(file_icon); } } [StructLayout(LayoutKind.Sequential)] public struct TaskList diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrEquip.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrEquip.cs index 90a33392f8..007fcd0393 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrEquip.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrEquip.cs @@ -819,7 +819,7 @@ namespace PerfectWorld.Scripts.Managers /// /// Read maker information from binary data /// - private void ReadMakerInfo(CECDataReader dr) + protected void ReadMakerInfo(CECDataReader dr) { // Debug: Log the bytes at current position before reading // We need to check what bytes are actually at the reader position diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrFlysword.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrFlysword.cs index 5e37a8b04c..ce0fbe94e0 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrFlysword.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrFlysword.cs @@ -1,20 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.IO; +using BrewMonster; +using ModelRenderer.Scripts.Common; +using ModelRenderer.Scripts.GameData; +using UnityEngine; +using PerfectWorld.Scripts.Managers; +using BrewMonster.Network; using BrewMonster.Scripts.Managers; +using BrewMonster.Scripts; +using CSNetwork.GPDataType; +using System.Runtime.InteropServices; namespace PerfectWorld.Scripts.Managers { - public class EC_IvtrFlysword : EC_IvtrItem + + /// + /// Flysword Item (Phi kiem). + /// + public class EC_IvtrFlysword : EC_IvtrEquip { + + protected int m_iCurTime; // Current time counter in ms + protected IVTR_ESSENCE_FLYSWORD m_Essence; + protected FLYSWORD_ESSENCE m_pDBEssence; /// - /// Not create logic yet (add summary later) + /// Flysword Item (Phi kiem) /// /// Template id /// Expire date public EC_IvtrFlysword(int tid, int expire_date) : base(tid, expire_date) { + m_iCID = (int)InventoryClassId.ICID_FLYSWORD; + + // Get database data + elementdataman pDB = ElementDataManProvider.GetElementDataMan(); + DATA_TYPE DataType = DATA_TYPE.DT_INVALID; + m_pDBEssence = (FLYSWORD_ESSENCE)pDB.get_data_ptr((uint)tid, ID_SPACE.ID_SPACE_ESSENCE, ref DataType); + m_iPileLimit = m_pDBEssence.pile_num_max; + m_iPrice = m_pDBEssence.price; + m_iShopPrice = m_pDBEssence.shop_price; + m_iProcType = (int)m_pDBEssence.proc_type; + m_i64EquipMask = EC_IvtrType.EQUIP_MASK64_FLYSWORD; + m_bUseable = true; + m_bNeedUpdate = false; } public EC_IvtrFlysword(EC_IvtrFlysword other) : base(other) { + m_iCurTime = other.m_iCurTime; + m_Essence = other.m_Essence; + m_pDBEssence = other.m_pDBEssence; + MadeFrom = other.MadeFrom; + Maker = other.Maker; } + + public override bool SetItemInfo(byte[] pInfoData, int iDataLen) + { + // Note: because fly sword isn't an absolute equipment, so skip + // CECIvtrEquip::SetItemInfo(). + + if (pInfoData == null || iDataLen == 0) + { + m_bNeedUpdate = false; + return true; + } + + try + { + CECDataReader dr = new CECDataReader(pInfoData, iDataLen); + + m_Essence = new IVTR_ESSENCE_FLYSWORD(dr.ReadData(Marshal.SizeOf())); + base.ReadMakerInfo(dr); + } + catch (Exception e) + { + Debug.LogError("CECIvtrFlySword::SetItemInfo, data read error (" + e.GetType() + ")"); + return false; + } + + LevelReq = m_Essence.require_level; + m_bNeedUpdate = false; + m_iCurTime = m_Essence.cur_time * 1000; + + return true; + } + + public override string GetIconFile() + { + return m_pDBEssence.FileIcon; + } + protected override string GetNormalDesc(bool bRepair) + { + if (m_bNeedUpdate) + return string.Empty; + + m_strDesc = ""; + + // Try to build item description + CECStringTab pDescTab = EC_Game.GetItemDesc(); + CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer(); + int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; + int red = (int)DescriptipionMsg.ITEMDESC_COL_RED; + int namecol = DecideNameCol(); + + if (m_iCount > 1) + AddDescText(namecol, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NAMENUMBER), GetName(), m_iCount); + else + AddDescText(namecol, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NAME), GetName()); + + AddIDDescText(); + + AddBindDescText(); + + // Is destroying? + AddDestroyingDesc((int)m_pDBEssence.id_drop_after_damaged, m_pDBEssence.num_drop_after_damaged); + + AddExpireTimeDesc(); + + // level + AddDescText(white, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_LEVEL), m_Essence.level); + + // whether can be improved + if (IsImprovable()) + AddDescText((int)DescriptipionMsg.ITEMDESC_COL_GREEN,true,pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_FLYSWORD_IMPROVE),m_Essence.improve_level,GetMaxImproveLevel()); + else + AddDescText(white,true,pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_FLYSWORD_NOIMPROVED)); + + // Normal fly speed bonus + if (m_Essence.speed_increase > 0) + AddDescText(white, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_ADDFLYSPEED), m_Essence.speed_increase); + + // Quick fly speed bonus + if (m_Essence.speed_increase2 > 0) + AddDescText(white, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_ADDFLYSPEED2), m_Essence.speed_increase2); + + // Remain time + if (GetMaxTime() > 0) + AddDescText(white, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_REMAINTIME), GetCurTime(), GetMaxTime()); + + // Profession requirement + AddProfReqDesc((uint)m_Essence.profession); + + // Level requirement + if (LevelReq > 0) + { + int col = pHost.GetMaxLevelSofar() >= LevelReq ? white : red; + AddDescText(col, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_LEVELREQ), LevelReq); + } + + // Element consume + // AddDescText(white, true, pDescTab->GetWideString(ITEMDESC_ELEMENTTIME), m_Essence.time_per_element); + + // Price + AddPriceDesc(white, bRepair); + + AddMakerDesc(); + + // Suite description + AddSuiteDesc(); + + // Extend description + AddExtDescText(); + + return m_strDesc; + } + // Get max time + protected int GetMaxTime() + { + return (int)m_Essence.max_time; + } + + // Get max element number + protected int GetMaxElement() + { + return (int)((float)m_Essence.max_time / m_Essence.time_per_element + 0.5f); + } + + // Get time each element equal to + protected int GetElementTime() + { + return m_Essence.time_per_element; + } + // Get number of element if time is filled to full. This is just the number + // of element which has been used + protected int GetUsedElementNum() + { + return (int)((m_Essence.max_time - GetCurTime()) / (float)m_Essence.time_per_element); + } + + // Get drop model for shown + protected string GetDropModel() + { + return m_pDBEssence.FileMatter; + } + public override bool IsRare() + { + return base.IsRare() || m_Essence.level >= 6; + } + protected bool IsImprovable() + { + return m_pDBEssence.max_improve_level > 0 && m_pDBEssence.improve_config[0].require_item_num > 0; + } + protected bool CanBeImproved() + { + if(m_pDBEssence.max_improve_level <=0) return false; + + if(m_Essence.improve_level>= GetMaxImproveLevel()) return false; + + return m_pDBEssence.improve_config[m_Essence.improve_level].require_item_num > 0; + } + protected int GetMaxImproveLevel() + { + int maxL = m_pDBEssence.improve_config.Length; + int i = 0; + for (i = 0; i < maxL; i++) + { + if(m_pDBEssence.improve_config[i].require_item_num == 0) + break; + } + return Mathf.Min(m_pDBEssence.max_improve_level, i); + } + + protected int GetCurTime() { return m_Essence.cur_time; } } } diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrPetEgg.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrPetEgg.cs index 4b1c0917e4..38dacf7996 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrPetEgg.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrPetEgg.cs @@ -194,8 +194,82 @@ namespace PerfectWorld.Scripts.Managers } private string GetRidingPetDesc(bool bRepair) { - Debug.Log("GetRidingPetDesc. This is not implemented yet."); - return ""; + if (m_bNeedUpdate) + return ""; + + m_strDesc = string.Empty; + + // Try to build item description + CECStringTab pDescTab = EC_Game.GetItemDesc(); + CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer(); + + int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; + int red = (int)DescriptipionMsg.ITEMDESC_COL_RED; + int namecol = DecideNameCol(); + + // Item name: always use the name in template + if (m_iCount > 1) + AddDescText(namecol, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NAMENUMBER), m_pDBEssence.name/* GetName() */, m_iCount); + else + AddDescText(namecol, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NAME), m_pDBEssence.name/* GetName() */); + + AddIDDescText(); + + AddBindDescText(); + + AddExpireTimeDesc(); + + // ������ɫ��Ϣ + if (m_pPetEssence.id != 0 && m_pPetEssence.require_dye_count > 0) + { + if (m_iScaleType == (int)ScaleType.SCALE_BUY) + { + AddDescText(white, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_COLOR)); + AddDescText(white, true, " ???"); + } + else + { + Color clr = new Color(255, 255, 255); + if (RIDINGPET.GetColor(m_Essence.color, clr)) + clr = RIDINGPET.GetDefaultColor(); + string strColor = string.Format("^{0:X2}{1:X2}{2:X2}", clr.r, clr.g, clr.b); + AddDescText(white, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_COLOR)); + m_strDesc += " "; + AddDescText(-1, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_COLORRECT), strColor); + } + } + + // Food type requirement + AddFoodTypeDesc(); + + if (m_pPetEssence.id != 0) + { + // Pet level + AddDescText(white, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_PETLEVEL), m_Essence.level); + + // Move speed + float fSpeed = m_pPetEssence.speed_a + (m_Essence.level - 1) * m_pPetEssence.speed_b; + AddDescText(-1, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_MOVESPEED), fSpeed); + + // Profession requirement + AddProfReqDesc(m_pPetEssence.character_combo_id); + } + + // Level requirement + int iLevelReq = Mathf.Max((int)m_Essence.level, m_Essence.req_level); + if (iLevelReq > 0) + { + int col = pHost.GetMaxLevelSofar() >= iLevelReq ? white : red; + AddDescText(col, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_LEVELREQ), iLevelReq); + } + + // Price + AddPriceDesc(white, bRepair); + + // Extend description + AddExtDescText(); + + return m_strDesc; } private string GetCombatPetDesc(bool bRepair) { diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrTaskDice.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrTaskDice.cs index ea1a6c2c09..934066e194 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrTaskDice.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrTaskDice.cs @@ -1,19 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.IO; +using BrewMonster; +using ModelRenderer.Scripts.Common; +using ModelRenderer.Scripts.GameData; +using UnityEngine; +using PerfectWorld.Scripts.Managers; +using BrewMonster.Network; using BrewMonster.Scripts.Managers; +using BrewMonster.Scripts; +using CSNetwork.GPDataType; +using System.Runtime.InteropServices; namespace PerfectWorld.Scripts.Managers { + /// + /// Task Dice Item (Tui qua random). + /// This is a part of IvtrTaskItem(C++) + /// public class EC_IvtrTaskDice : EC_IvtrItem { + protected TASKDICE_ESSENCE m_pDBEssence; + /// - /// Not create logic yet (add summary later) + /// Task Dice Item /// - /// Template id - /// Expire date - public EC_IvtrTaskDice(int tid, int expire_date) : base(tid, expire_date) + /// + /// + public EC_IvtrTaskDice(int tid, int expire_date) : base(tid, expire_date) { - } + m_iCID = (int)InventoryClassId.ICID_TASKDICE; + elementdataman pDB = ElementDataManProvider.GetElementDataMan(); + DATA_TYPE DataType = DATA_TYPE.DT_INVALID; + m_pDBEssence = (TASKDICE_ESSENCE)pDB.get_data_ptr((uint)tid, ID_SPACE.ID_SPACE_ESSENCE, ref DataType); + m_iPileLimit = m_pDBEssence.pile_num_max; + m_iPrice = 0; + m_iShopPrice = 0; + m_iProcType = (int)m_pDBEssence.proc_type; + m_bUseable = true; + m_bNeedUpdate = false; + } public EC_IvtrTaskDice(EC_IvtrTaskDice other) : base(other) { + m_pDBEssence = other.m_pDBEssence; + } + public override bool SetItemInfo(byte[] pInfoData, int iDataLen) + { + base.SetItemInfo(pInfoData, iDataLen); + return true; + } + public override string GetIconFile() + { + return m_pDBEssence.FileIcon; + } + protected override string GetNormalDesc(bool bRepair) + { + m_strDesc = ""; + // Try to build item description + CECStringTab pDescTab = EC_Game.GetItemDesc(); + int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; + int namecol = DecideNameCol(); + + if (m_iCount > 1) + AddDescText(namecol, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NAMENUMBER), GetName(), m_iCount); + else + AddDescText(namecol, true, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NAME), GetName()); + AddIDDescText(); + AddExpireTimeDesc(); + + AddPriceDesc(white, bRepair); + + // Extend description + AddExtDescText(); + + return m_strDesc; + } + public override string GetDropModel() + { + return m_pDBEssence.FileMatter; } } } diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrType.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrType.cs index 81d67669b0..07e9ab0e01 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrType.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrType.cs @@ -160,16 +160,28 @@ namespace BrewMonster.Scripts.Managers }; public struct IVTR_ESSENCE_FLYSWORD { - // TODO : implement data later - // int cur_time; - // int max_time; - // short require_level; - // char level; - // char improve_level; - // int profession; - // size_t time_per_element; - // float speed_increase; - // float speed_increase2; + public int cur_time; + public int max_time; + public short require_level; + public char level; + public char improve_level; + public int profession; + public int time_per_element; + public float speed_increase; + public float speed_increase2; + public IVTR_ESSENCE_FLYSWORD(byte[] data) + { + CECDataReader dr = new (data, data.Length); + cur_time = dr.ReadInt(); + max_time = dr.ReadInt(); + require_level = dr.ReadShort(); + level = (char)dr.ReadByte(); + improve_level = (char)dr.ReadByte(); + profession = dr.ReadInt(); + time_per_element = dr.ReadInt(); + speed_increase = dr.ReadFloat(); + speed_increase2 = dr.ReadFloat(); + } }; public struct IVTR_ESSENCE_WING { diff --git a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs index 37e7fe578a..7fa718f687 100644 --- a/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs +++ b/Assets/PerfectWorld/Scripts/Move/CECPlayer.cs @@ -81,6 +81,7 @@ namespace BrewMonster protected int m_iBoothState = 0; // Booth state. 0, none; 1, prepare; 2, open booth; 3, visite other's booth public int m_idFRole = GNETRoles._R_UNMEMBER; // ID of player's faction role + protected int m_idCountry = 0; // ¹úÕ½ÕóÓª id public static int MAX_REINCARNATION = 2; protected List m_aCurEffects = new List(); // Current effects @@ -1905,4 +1906,42 @@ namespace BrewMonster enumSkinShowArmet, enumSkinShowHand, }; + /// + /// Place holder for riding pet. Not test logic yet. + /// + public struct RIDINGPET + { + public int id; + public ushort color; + + public RIDINGPET(bool isReset = true) + { + id = 0; + color = 0; + } + + public bool GetColor(Color clr) + { + // ��ȡ��ǰ����Ⱦɫ��ɫ + return id>0 && GetColor(color, clr); + } + + public static bool GetColor(ushort c, Color clr) + { + // ��ѯ��ǰ����Ƿ�Ⱦ��ɫ�����Ⱦ��ɫ�����ر�Ⱦ֮�����ɫ + bool bRet = false; + if ((c & (1<<(8-1))) != 0) + { + // unsigned short ��λΪ1ʱ����ʾȾ��ɫ����ʱ�ɻ�ȡ��ɫ + clr = new Color(((c) & (0x1f << 10)) >> 7, ((c) & (0x1f << 5)) >> 2, ((c) & 0x1f) << 3); + bRet = true; + } + return bRet; + } + + public static Color GetDefaultColor() + { + return new Color(255, 255, 255); + } + }; } \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs index 56ec20638a..bde265edbb 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs @@ -1,1536 +1,1768 @@ -using BrewMonster; -using BrewMonster.Common; -using BrewMonster.Managers; -using BrewMonster.Network; -using BrewMonster.Scripts.Skills; -using BrewMonster.UI; -using CSNetwork.C2SCommand; -using CSNetwork.GPDataType; -using CSNetwork.Protocols; -using CSNetwork.Protocols.RPCData; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using UnityEngine; -using CommandID = CSNetwork.GPDataType.CommandID; - -namespace CSNetwork -{ - public class GameSession : IDisposable - { - private static IPrefixedLogger - _logger = LoggerFactory.GetLogger(nameof(GameSession)); // Get class-specific logger - - private NetworkManager _networkManager; - private string _host; - private int _port; - private string _username; - private string _password; - private int _currentUserId = -1; // To store the UserID after successful login - private int m_iCharID; - private int m_idLastSelTarget = 0; // ID of selected item last time - - CECStringTab m_ErrorMsgs; - - // State management for async operations and callbacks - private Action _loginCallback; - private Action> _roleListCallback; - private List _accumulatedRoles; - private Action _selectRoleCallback; - private RoleInfo _selectedRole; - public bool IsConnected => _networkManager?.IsConnected ?? false; - public static SynchronizationContext Context; - private CECC2SCmdCache m_CmdCache; // C2S command cache -#if UNITY_EDITOR - public bool isDebug; - public bool IsDebug - { - get => isDebug; - set => isDebug = value; - } -#endif - public CECC2SCmdCache CmdCache { get => m_CmdCache; } - - - public GameSession() - { - _networkManager = new NetworkManager(); - m_CmdCache = new CECC2SCmdCache(); - _networkManager.ProtocolReceived += OnProtocolReceived; - _networkManager.ErrorOccurred += OnErrorOccurred; - _networkManager.Disconnected += OnDisconnected; - } - - public void SetLogPath(string path) - { - LoggerFactory.SetFileLoggerImplementation(new FileLogger()); - _logger = LoggerFactory.GetCustomLogger(path, nameof(GameSession) + GetHashCode(), LoggerType.File); - _networkManager.SetLogPath(path); - } - - /// - /// Connects to the game server asynchronously. - /// - /// Server hostname or IP address. - /// Server port. - /// Task representing the asynchronous connect operation. Check IsConnected property or handle Disconnected event for status. - public async Task ConnectAsync(string host, int port) - { - if (IsConnected) - { - _logger.Log(LogType.Warning, "ConnectAsync called but already connected."); - return; - } - - _host = host; - _port = port; - _logger.Log(LogType.Info, $"Attempting to connect to {_host}:{_port}..."); - try - { - await _networkManager.ConnectAsync(_host, _port); - if (IsConnected) - { - _logger.Log(LogType.Info, "Connection established."); - } - else - { - _logger.Log(LogType.Warning, - "Connection failed after ConnectAsync completed (check NetworkManager logs/events)."); - } - } - catch (Exception ex) - { - _logger.Log(LogType.Error, $"Connection exception: {ex.Message}"); - _logger.LogException(ex); - OnDisconnected(); - } - } - - - public void Disconnect() - { - _networkManager.Disconnect(); - } - - /// - /// Initiates the login process asynchronously. - /// - /// Account username. - /// Account password. - /// Action invoked with true on successful login (OnlineAnnounce received), false otherwise. - public void LoginAsync(string username, string password, Action callback) - { - if (!IsConnected) - { - _logger.Log(LogType.Warning, "LoginAsync called but not connected."); - callback?.Invoke(false); - return; - } - - if (_loginCallback != null) - { - _logger.Log(LogType.Warning, "LoginAsync called while another login is already in progress."); - callback?.Invoke(false); - return; - } - - _username = username; - _password = password; - _loginCallback = callback; - _currentUserId = -1; // Reset user ID - - _logger.Log(LogType.Info, $"Initiating login for user '{_username}'..."); - } - - /// - /// Initiates fetching the role list asynchronously. Requires successful login. - /// - /// Action invoked with the complete list of roles, or null/empty list on failure. - public void GetRoleListAsync(Action> callback) - { - if (!IsConnected) - { - _logger.Log(LogType.Warning, "GetRoleListAsync called but not connected."); - callback?.Invoke(null); - return; - } - - if (_currentUserId == -1) - { - _logger.Log(LogType.Warning, "GetRoleListAsync called but not logged in."); - callback?.Invoke(null); - return; - } - - if (_roleListCallback != null) - { - _logger.Log(LogType.Warning, - "GetRoleListAsync called while another role list retrieval is already in progress."); - callback?.Invoke(null); - return; - } - - _roleListCallback = callback; - _accumulatedRoles = new List(); - _logger.Log(LogType.Info, "Requesting role list..."); - RequestRoleListInternal(); - } - - public RoleInfo GetRoleInfo() - { - return _selectedRole; - } - - public void SelectRoleAsync(RoleInfo role, Action callback) - { - _selectedRole = role; - _selectRoleCallback = callback; - SetCharacterID(role.roleid); - SendProtocol(new selectrole() - { - Roleid = role.roleid, - Flag = 0 - }); - } - - public void EnterWorldAsync(RoleInfo role, Action callback) - { - SendProtocol(new enterworld() - { - Roleid = _selectedRole.roleid, - Provider_link_id = 0, - }, callback); - } - - public void RequestDropIvtrItem(byte index, int amount) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.CreateDropIvtrItem(index, amount); - SendProtocol(gamedatasendRequest); - } - - public void RequestDropEquipItem(byte index) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.CreateDropEquipItem(index); - SendProtocol(gamedatasendRequest); - } - - public void RequestPickupItem(int idItem, int tid) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.CreatePickupItem(idItem, tid); - SendProtocol(gamedatasendRequest); - } - - public void c2s_SendCmdGetIvtrDetailData(byte byPackage, Action callback) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdGetIvtrDetailData(byPackage); - SendProtocol(gamedatasendRequest, callback); - } - - public void c2s_SendCmdQueryCashInfo() - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdQueryCashInfo(); - SendProtocol(gamedatasendRequest); - } - - public void c2s_SendCmdOpenFashionTrash(string password) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdOpenFashionTrash(password); - SendProtocol(gamedatasendRequest); - } - - public void c2s_SendCmdEquipItem(byte iIvtrIdx, byte iEquipIdx, Action callback) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdEquipItem(iIvtrIdx, iEquipIdx); - SendProtocol(gamedatasendRequest, callback); - } - - public void c2s_SendCmdReviveVillage(int param = 0) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); - SendProtocol(gamedatasendRequest); - } - public void c2s_SendCmdReviveItem(int param = 0) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); - SendProtocol(gamedatasendRequest); - } - public void RequestReviveByPlayer(int param = 0) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); - SendProtocol(gamedatasendRequest); - } - - - public void c2s_SendCmdMallShopping(uint count, CMD_MallShopping.goods[] goodsArray) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.CreateGetMallShopping(count, goodsArray); - SendProtocol(gamedatasendRequest); - } - public void c2s_SendCmdGatherMaterial(int idMatter, int iToolPack, int idToolIndex, int idTool, int idTask) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdGatherMaterial(idMatter, iToolPack, idToolIndex, idTool, idTask); - SendProtocol(gamedatasendRequest); - } - - public void RequestOwnItemInfoAsync( - byte byPackage, - byte bySlot, - int type, - int expire_date, - int state, - uint count, - ushort crc, - ushort content_length, - byte[] content, - Action callback) - { - gamedatasend gamedatasendRequest = new gamedatasend(); - gamedatasendRequest.Data = C2SCommandFactory.CreateOwnItemInfo(byPackage, bySlot, type, expire_date, state, - count, crc, content_length, content); - SendProtocol(gamedatasendRequest, callback); - } - - // --- Protocol Sending --- - public void SendProtocol(Protocol protocol, Action complete = null) - { - if (IsConnected) - { - _logger.Log(LogType.Debug, - $"Sending protocol: {protocol.GetType().Name} (Detail: {protocol.ToString})"); - _networkManager.Send(protocol); - complete?.Invoke(); - } - else - { - _logger.Log(LogType.Warning, $"Cannot send protocol ({protocol.GetType().Name}), not connected."); - } - } - - // --- Event Handlers (from NetworkManager) --- - - private void OnProtocolReceived(Protocol protocol) - { - _logger.Log(LogType.Debug, $"Received protocol: {protocol.GetType().Name} (Type: {protocol.Type})"); - if (protocol is null) - return; - - - // Route protocol to appropriate handler - switch (protocol.GetPType()) - { - case ProtocolType.PROTOCOL_CHALLENGE: - HandleChallenge((challenge)protocol); - break; - case ProtocolType.PROTOCOL_KEYEXCHANGE: - HandleKeyExchange((KeyExchange)protocol); - break; - case ProtocolType.PROTOCOL_ONLINEANNOUNCE: - HandleOnlineAnnounce((onlineannounce)protocol); - break; - case ProtocolType.PROTOCOL_ROLELIST_RE: - HandleRoleListResponse((RoleListResponse)protocol); - break; - // Add cases for other protocols GameSession might need to handle - case ProtocolType.PROTOCOL_SELECTROLE_RE: - HandleSelectRoleResponse((SelectRole_Re)protocol); - //_networkManager.IgnoreBytes = 2; - break; - case ProtocolType.PROTOCOL_S2CGAMEDATASEND: - case ProtocolType.PROTOCOL_GAMEDATASEND: - HandleServerDataSend((gamedatasend)protocol); - break; - case ProtocolType.PROTOCOL_CHATMESSAGE: - _logger.Log(LogType.Warning, $"HoangDev :ProtocolType.PROTOCOL_CHATMESSAGE {protocol.GetPType()}"); - OnPrtcChatMessage(protocol, false); - break; - case ProtocolType.PROTOCOL_PLAYERBASEINFO_RE: - OnPrtcPlayerBaseInfoRe(protocol); - break; - case ProtocolType.PROTOCOL_GETUICONFIG_RE: OnPrtcGetConfigRe(protocol); break; - - case ProtocolType.PROTOCOL_AUTOTEAMSETGOAL_RE: - { - // CECAutoTeam pAutoTeam = CECGameRun.Instance.GetHostPlayer().GetAutoTeam(); - // if( pAutoTeam !=null) - // pAutoTeam.OnPrtcAutoTeamSetGoalRe((AutoTeamSetGoal_Re)protocol); - } - break; - - default: - _logger.Log(LogType.Warning, $"Received unhandled protocol type: {protocol.GetPType()}"); - break; - } - } - - private void HandleServerDataSend(gamedatasend protocol) - { - int lenghtHeader = Marshal.SizeOf(); - var pDataBuf = new byte[protocol.Data.ByteArray.Length - lenghtHeader]; - var byteArrHeader = new byte[lenghtHeader]; - long dwDataSize = protocol.Data.Size; - - if (dwDataSize < Marshal.SizeOf()) - { - _logger.Error($"### GameDataSend: size invalid {dwDataSize}"); - return; - } - - dwDataSize -= Marshal.SizeOf(); // subtract the header size (ushort) - for (int i = 0; i < protocol.Data.ByteArray.Length; i++) - { - if (i < lenghtHeader) - { - byteArrHeader[i] = protocol.Data.ByteArray[i]; - } - else - { - pDataBuf[i - lenghtHeader] = protocol.Data.ByteArray[i]; - } - } - - var pCmdHeader = BitConverter.ToUInt16(byteArrHeader); - //sss -#if UNITY_EDITOR - if (isDebug) - { - BMLogger.LogError($"### GameDataSend: CMDID {pCmdHeader}"); - } -#endif - int iHostID = _selectedRole.roleid; - switch (pCmdHeader) - { - case CommandID.PLAYER_INFO_2: - case CommandID.PLAYER_INFO_3: - case CommandID.PLAYER_INFO_4: - case CommandID.PLAYER_INFO_2_LIST: - case CommandID.PLAYER_INFO_3_LIST: - case CommandID.PLAYER_INFO_23_LIST: - - break; - - case CommandID.PLAYER_INFO_1: - case CommandID.PLAYER_ENTER_WORLD: - case CommandID.PLAYER_ENTER_SLICE: - case CommandID.PLAYER_INFO_1_LIST: - case CommandID.PLAYER_INFO_00: - case CommandID.SELF_INFO_1: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERINFO, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, - pCmdHeader, iHostID); - break; - case CommandID.OBJECT_MOVE: - int lenghtDataType = Marshal.SizeOf(); - byte[] arrByteData = GetBytes(pDataBuf, lenghtDataType, 0); - int idObjMove = BitConverter.ToInt32(arrByteData); - if (ISPLAYERID(idObjMove)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERMOVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, - pDataBuf, pCmdHeader, iHostID); - } - else if (ISNPCID(idObjMove)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCMOVE, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, - pCmdHeader); - } - - break; - case CommandID.OBJECT_STOP_MOVE: - { - int id1 = GPDataTypeHelper.FromBytes(pDataBuf); - - if (ISPLAYERID(id1)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERSTOPMOVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, - pDataBuf, pCmdHeader); - } - else if (ISNPCID(id1)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCSTOPMOVE, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, - pCmdHeader); - } - - break; - } - case CommandID.OBJECT_LEAVE_SLICE: - { - int id = GPDataTypeHelper.FromBytes(pDataBuf); - if (ISPLAYERID(id)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERRUNOUT, (int)MANAGER_INDEX.MAN_PLAYER, -1, - pDataBuf, pCmdHeader); - } - else if (ISNPCID(id)) - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCRUNOUT, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, - pCmdHeader); - } - - break; - } - case CommandID.OWN_IVTR_DATA: - case CommandID.OWN_IVTR_DETAIL_DATA: - case CommandID.GET_OWN_MONEY: - case CommandID.CHANGE_IVTR_SIZE: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader, iHostID); - break; - case CommandID.EXG_IVTR_ITEM: - case CommandID.MOVE_IVTR_ITEM: - case CommandID.PLAYER_DROP_ITEM: - case CommandID.EXG_EQUIP_ITEM: - case CommandID.EQUIP_ITEM: - case CommandID.MOVE_EQUIP_ITEM: - case CommandID.UNFREEZE_IVTR_SLOT: - case CommandID.PLAYER_EQUIP_TRASHBOX_ITEM: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ITEMOPERATION, (int)MANAGER_INDEX.MAN_PLAYER, 0, - pDataBuf, pCmdHeader); - break; - case CommandID.PLAYER_CASH: - { - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader, iHostID); - break; - } - case CommandID.MATTER_INFO_LIST: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERINFO, (int)MANAGER_INDEX.MAN_MATTER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.MATTER_ENTER_WORLD: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERENTWORLD, (int)MANAGER_INDEX.MAN_MATTER, 0, - pDataBuf, pCmdHeader); - break; - case CommandID.PICKUP_ITEM: - case CommandID.HOST_OBTAIN_ITEM: - case CommandID.PRODUCE_ONCE: - case CommandID.TASK_DELIVER_ITEM: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PICKUPITEM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.MATTER_PICKUP: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PICKUPMATTER, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.PICKUP_MONEY: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PICKUPMONEY, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.HOST_CORRECT_POS: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CORRECTPOS, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader, iHostID); - break; - case CommandID.OWN_ITEM_INFO: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_OWNITEMINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader, iHostID); - break; - case CommandID.PLAYER_DIED: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDIED, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.HOST_DIED: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_DIED, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.PLAYER_REVIVE: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERREVIVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.NOTIFY_HOSTPOS: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_GOTO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.NPC_ENTER_SLICE: - case CommandID.NPC_INFO_LIST: - case CommandID.NPC_INFO_00: - case CommandID.NPC_ENTER_WORLD: - case CommandID.NPC_VISIBLE_TID_NOTIFY: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCINFO, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, - pCmdHeader, dwDataSize); - break; - case CommandID.TASK_DATA: - case CommandID.TASK_VAR_DATA: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TASKDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader, dwDataSize); - break; - case CommandID.BE_HURT: - case CommandID.HURT_RESULT: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_HURTRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.OBJECT_ATTACK_RESULT: - //int id = GPDataTypeHelper.FromBytes(pDataBuf); - cmd_object_atk_result pCmdAtk = GPDataTypeHelper.FromBytes(pDataBuf); - //BMLogger.LogError($"OBJECT_ATTACK_RESULT: npc ? " + ISNPCID(id)); - - if (ISPLAYERID(pCmdAtk.attacker_id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERATKRESULT, MANAGER_INDEX.MAN_PLAYER, -1, - pDataBuf, pCmdHeader); - else if (ISNPCID(pCmdAtk.attacker_id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCATKRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - break; - case CommandID.HOST_ATTACKRESULT: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ATKRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.HOST_ATTACKED: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ATTACKED, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - - case CommandID.ERROR_MESSAGE: - { - int errRaw = BitConverter.ToInt32(pDataBuf, 0); - // Note: _logger may be configured as a file logger via SetLogPath(), so also log to console for visibility. - _logger.Info($"### GameDataSend: ERROR_MESSAGE: {errRaw}"); -#if UNITY_EDITOR - BMLogger.LogError($"### GameDataSend: ERROR_MESSAGE: {errRaw}"); -#endif - cmd_error_msg pCmd = GPDataTypeHelper.FromBytes(pDataBuf); -#if UNITY_EDITOR - BMLogger.LogError($"### GameDataSend: ERROR_MESSAGE parsed iMessage={pCmd.iMessage}"); -#endif - - if (pCmd.iMessage != 0) - { - // string szMsg = m_ErrorMsgs.GetWideString(pCmd.iMessage); - // if (string.IsNullOrEmpty(szMsg)) - // BMLogger.LogError("SERVER - unknown error !"); - //else if (pCmd.iMessage != 2) - //g_pGame.GetGameRun().AddChatMessage(szMsg, GP_CHAT_MISC); - } - - if (pCmd.iMessage == 2) - { - // Attack target is too far - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TARGETISFAR, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - } - else if (pCmd.iMessage == 20) - { - // Failed to cast skill - //pGameRun.PostMessage(MSG_PM_CASTSKILL, MAN_PLAYER, 0, (DWORD)pDataBuf, pCmdHeader.cmd); - } - else if (pCmd.iMessage == 133 || pCmd.iMessage == 134) - { - // deal failed - //pGameRun.PostMessage(MSG_HST_BUY_SELL_FAIL, MAN_PLAYER, 0, (DWORD)pDataBuf, pCmdHeader.cmd); - } - else if (pCmd.iMessage == 158) - { - // µ±Ç°»ãÂʲ»¶Ô£¬ÖØÐÂÈ¡»ãÂÊ - //c2s_CmdGetCashMoneyRate(); - } - else if (pCmd.iMessage == 108 /*&& pGameRun.GetHostPlayer().IsInKingService()*/) - { - /* CECGameUIMan* pGameUI = pGameRun.GetUIManager().GetInGameUIMan(); - if (pGameUI) - pGameUI.EndNPCService();*/ - } - else if - (pCmd.iMessage == 108 /*&& pGameRun.GetHostPlayer().GetOfflineShopCtrl().GetNPCSevFlag() != COfflineShopCtrl::NPCSEV_NULL*/ - ) - { - /* CECGameUIMan* pGameUI = pGameRun.GetUIManager().GetInGameUIMan(); - if (pGameUI) - pGameUI.EndNPCService();*/ - } - else if (pCmd.iMessage == 175) - { - //c2s_CmdQueryParallelWorld(); - } - else if (pCmd.iMessage == 6) - { - //AP_ActionEvent(AP_EVENT_CANNOTPICKUP); - } - - break; - } - case CommandID.SELECT_TARGET: - case CommandID.UNSELECT: - - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SELTARGET, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.NPC_DIED: - case CommandID.NPC_DIED2: - - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCDIED, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - break; - case CommandID.OBJECT_DISAPPEAR: - { - int lenghtDataType1 = Marshal.SizeOf(); - byte[] arrByteData1 = GetBytes(pDataBuf, lenghtDataType1, 0); - int objectId = BitConverter.ToInt32(arrByteData1); - if (ISPLAYERID(objectId)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDISAPPEAR, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - else if (ISNPCID(objectId)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCDISAPPEAR, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - else if (ISMATTERID(objectId)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERDISAPPEAR, MANAGER_INDEX.MAN_MATTER, 0, pDataBuf, pCmdHeader); - - break; - } - case CommandID.SELF_INFO_00: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_INFO00, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, - pCmdHeader); - break; - case CommandID.NPC_GREETING: - { - // If this greeting is from the skill-learn NPC, record it (C++ skill dialog relies on this). - try - { - cmd_npc_greeting greet = GPDataTypeHelper.FromBytes(pDataBuf); - CECHostSkillModel.Instance.OnNpcGreeting(greet.idObject); - } - catch (Exception ex) - { - _logger.Log(LogType.Warning, $"Failed to parse NPC_GREETING payload: {ex.Message}"); - } - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_NPCGREETING, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - } - case CommandID.ACTIVATE_WAYPOINT: - case CommandID.WAYPOINT_LIST: - - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_WAYPOINT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.SERVER_TIME: - { - cmd_server_time pcmd_server_time = GPDataTypeHelper.FromBytes(pDataBuf); - EC_ManMessage.PostMessage(EC_MsgDef.MSG_SERVERTIME, -1, 0, pcmd_server_time.time, pcmd_server_time.timebias); - break; - } - case CommandID.SCENE_SERVICE_NPC_LIST: - { - CECHostSkillModel.Instance.RecvNPCServiceList(protocol.Data); - break; - } - case CommandID.SKILL_DATA: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.OBJECT_CAST_SKILL: - case CommandID.OBJECT_CAST_INSTANT_SKILL: - case CommandID.OBJECT_CAST_POS_SKILL: - { - cmd_object_cast_skill pCmd2 = GPDataTypeHelper.FromBytes(pDataBuf,true); - if (ISPLAYERID(pCmd2.caster)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_CASTSKILL, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - else if (ISNPCID(pCmd2.caster)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCCASTSKILL, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - - break; - } - case CommandID.LEVEL_UP: - { - cmd_level_up pCmdLevelUp = GPDataTypeHelper.FromBytes(pDataBuf); ; - if (ISPLAYERID(pCmdLevelUp.id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERLEVELUP, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - else if (ISNPCID(pCmdLevelUp.id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCLEVELUP, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - break; - } - case CommandID.HOST_START_ATTACK: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_STARTATTACK, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); - break; - case CommandID.HOST_STOPATTACK: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_STOPATTACK, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); - break; - case CommandID.HOST_SKILL_ATTACK_RESULT: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLRESULT, 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); - break; - case CommandID.ENCHANT_RESULT: - cmd_enchant_result pCmd3 = GPDataTypeHelper.FromBytes(pDataBuf); - if (ISPLAYERID(pCmd3.caster)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_ENCHANTRESULT, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - else if (ISNPCID(pCmd3.caster)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_ENCHANTRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); - break; - case CommandID.SKILL_PERFORM: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_CASTSKILL, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.SET_COOLDOWN: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SETCOOLTIME, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.COMBO_SKILL_PREPARE: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_COMBO_SKILL_PREPARE, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); - break; - case CommandID.PLAYER_EXT_PROP_BASE: - case CommandID.PLAYER_EXT_PROP_MOVE: - case CommandID.PLAYER_EXT_PROP_ATK: - case CommandID.PLAYER_EXT_PROP_DEF: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEREXTPROP, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.OWN_EXT_PROP: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_OWNEXTPROP, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.OBJECT_DO_EMOTE: - case CommandID.OBJECT_EMOTE_RESTORE: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDOEMOTE, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.OUT_OF_SIGHT_LIST: - { - cmd_out_of_sight_list pCmd5 = default; - pCmd5.uCount = GPDataTypeHelper.FromBytes(pDataBuf); - int offset2 = sizeof(uint); - pCmd5.idList = new int[pCmd5.uCount]; - for (int i = 0; i < pCmd5.uCount; i++) - { - pCmd5.idList[i] = GPDataTypeHelper.FromBytes(pDataBuf, offset2); - offset2 += 4;//sizeof int; - } - - for (uint n = 0; n < pCmd5.uCount; n++) - { - if (ISPLAYERID(pCmd5.idList[n])) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEROUTOFVIEW, MANAGER_INDEX.MAN_PLAYER, -1, pCmd5.idList[n], pCmdHeader); - else if (ISNPCID(pCmd5.idList[n])) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCOUTOFVIEW, MANAGER_INDEX.MAN_NPC, 0, pCmd5.idList[n], pCmdHeader); - else if (ISMATTERID(pCmd5.idList[n])) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTEROUTOFVIEW, MANAGER_INDEX.MAN_MATTER, 0, pCmd5.idList[n], pCmdHeader); - } - - break; - } - case CommandID.PLAYER_GATHER_START: - case CommandID.PLAYER_GATHER_STOP: - case CommandID.MINE_GATHERED: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERGATHER, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - case CommandID.COOLTIME_DATA: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_COOLTIMEDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.OBJECT_TAKEOFF: - { - cmd_object_takeoff pCmdTakeOff = GPDataTypeHelper.FromBytes((byte[])pDataBuf); - if (ISPLAYERID(pCmdTakeOff.object_id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - } - case CommandID.OBJECT_LANDING: - { - cmd_object_landing pCmdLanding = GPDataTypeHelper.FromBytes((byte[])pDataBuf); - if (ISPLAYERID(pCmdLanding.object_id)) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); - break; - } - case CommandID.HOST_RUSH_FLY: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.FLYSWORD_TIME: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_FLYSWORDTIME, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.PRODUCE_START: - case CommandID.PRODUCE_END: - case CommandID.PRODUCE_NULL: - // Post MSG_HST_PRODUCEITEM message with command ID as parameter (matches C++ behavior) - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PRODUCEITEM, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - - - case CommandID.LEARN_SKILL: - BMLogger.LogError("### GameDataSend: LEARN_SKILL"); - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_LEARNSKILL, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - case CommandID.EMBED_ITEM: - EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_EMBEDITEM, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); - break; - default: -#if UNITY_EDITOR - if (isDebug) - { - BMLogger.LogError($"### GameDataSend: Unhandled CMDID {pCmdHeader} (payloadBytes={pDataBuf?.Length ?? 0})"); - } -#endif - break; - } - } - - - private void HandleSelectRoleResponse(SelectRole_Re protocol) - { - _logger.Info($"Select role response {protocol.result}"); - _selectRoleCallback?.Invoke(_selectedRole); - } - - private void OnErrorOccurred(string errorMessage) - { - _logger.Log(LogType.Error, $"Network Error: {errorMessage}"); - FailLoginInProgress(errorMessage); - FailRoleListInProgress(errorMessage); - } - - private void OnDisconnected() - { - _logger.Log(LogType.Info, "Disconnected from server."); - _currentUserId = -1; - FailLoginInProgress("Disconnected"); - FailRoleListInProgress("Disconnected"); - // Clear command cache - m_CmdCache.RemoveAllCmds(); - } - - // --- Protocol Handling Logic --- - - private void HandleChallenge(challenge challenge) - { - if (_loginCallback == null || string.IsNullOrEmpty(_username)) - { - _logger.Log(LogType.Warning, "Received Challenge but not expecting it or username not set."); - return; - } - - _logger.Log(LogType.Info, "Handling Challenge..."); - - response response = new response(); - byte[] usernameBytes = Encoding.ASCII.GetBytes(_username); - byte[] passwordBytes = Encoding.ASCII.GetBytes(_password); - response.identity.Replace(usernameBytes); - response.Setup(new Octets(usernameBytes), new Octets(passwordBytes), challenge.nonce); - - uint clientId = 0xffffffff; - byte[] clientIdBytes = BitConverter.GetBytes(clientId); - response.cli_fingerprint.Replace(clientIdBytes); - response.use_token = 0; - - _networkManager.SetNonce(response.response_data); - SendProtocol(response); - _logger.Log(LogType.Info, "Sent Response."); - } - - private void HandleKeyExchange(KeyExchange keyExchange) - { - if (_loginCallback == null || string.IsNullOrEmpty(_username)) - { - _logger.Log(LogType.Warning, "Received KeyExchange but not expecting it."); - return; - } - - _logger.Log(LogType.Info, "Handling KeyExchange..."); - keyExchange.Setup(_networkManager, _username); - keyExchange.Blkickuser = 1; - SendProtocol(keyExchange); - _logger.Log(LogType.Info, "Sent KeyExchange acknowledgment/response."); - } - - private void HandleOnlineAnnounce(onlineannounce announce) - { - if (_loginCallback == null) - { - _logger.Log(LogType.Warning, "Received OnlineAnnounce but not expecting it."); - return; - } - - _logger.Log(LogType.Info, $"Login successful! UserID: {announce.Userid}, LocalSID: {announce.Localsid}"); - _currentUserId = announce.Userid; - - var callback = _loginCallback; - _loginCallback = null; - callback?.Invoke(true); - } - - private void RequestRoleListInternal(int lastHandle = -1) - { - rolelist rolelistRequest = new rolelist(); - rolelistRequest.Userid = _currentUserId; - rolelistRequest.Localsid = 0; - rolelistRequest.Handle = lastHandle; - - SendProtocol(rolelistRequest); - - - //gamedatasend gamedatasendRequest = new gamedatasend(); - //gamedatasendRequest.Data = C2SCommandFactory.CreatePlayerMove(); - - //SendProtocol(gamedatasendRequest); - } - - private void HandleRoleListResponse(RoleListResponse response) - { - if (_roleListCallback == null || _accumulatedRoles == null) - { - _logger.Log(LogType.Warning, "Received RoleListResponse but not expecting it."); - return; - } - - _logger.Log(LogType.Debug, - $"Received RoleListResponse. Handle: {response.handle}, Result: {response.result}, Count: {response.rolelist.Count}"); - - if (response.result == 0) - { - _accumulatedRoles.AddRange(response.rolelist); - - foreach (var role in response.rolelist) - { - try - { - string roleName = Encoding.UTF8.GetString(role.name.ByteArray, 0, role.name.Length); - _logger.Log(LogType.Info, $" - Role ID: {role.roleid}, Name: {roleName}, Level: {role.level}"); - } - catch (Exception ex) - { - _logger.Log(LogType.Error, $" - Error decoding role name: {ex.Message}"); - _logger.LogException(ex); - } - } - - if (response.handle != -1) - { - _logger.Log(LogType.Debug, $"Requesting next batch of roles (handle: {response.handle})..."); - RequestRoleListInternal(response.handle); - } - else - { - _logger.Log(LogType.Info, $"Finished fetching roles. Total count: {_accumulatedRoles.Count}"); - var callback = _roleListCallback; - var result = _accumulatedRoles; - _roleListCallback = null; - _accumulatedRoles = null; - callback?.Invoke(result); - } - } - else - { - _logger.Log(LogType.Error, $"Role list retrieval failed. Result code: {response.result}"); - FailRoleListInProgress($"Role list retrieval failed (Result: {response.result})"); - } - } - - // --- Helper methods for failure handling --- - private void FailLoginInProgress(string reason) - { - if (_loginCallback != null) - { - _logger.Log(LogType.Error, $"Login failed: {reason}"); - var callback = _loginCallback; - _loginCallback = null; - callback?.Invoke(false); - } - } - - private void FailRoleListInProgress(string reason) - { - if (_roleListCallback != null) - { - _logger.Log(LogType.Error, $"Role list retrieval failed: {reason}"); - var callback = _roleListCallback; - _roleListCallback = null; - _accumulatedRoles = null; - callback?.Invoke(null); - } - } - - // --- IDisposable Implementation --- - private bool disposedValue = false; - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - if (_networkManager != null) - { - _logger.Log(LogType.Info, "[DUCK] Disposing GameSession and disconnecting..."); - _networkManager.ProtocolReceived -= OnProtocolReceived; - _networkManager.ErrorOccurred -= OnErrorOccurred; - _networkManager.Disconnected -= OnDisconnected; - _networkManager.Disconnect(); - _networkManager.Dispose(); - _networkManager = null; - } - - _loginCallback = null; - _roleListCallback = null; - _accumulatedRoles = null; - } - - disposedValue = true; - } - } - - public void Dispose() - { - Dispose(true); - // GC.SuppressFinalize(this); - } - - public bool ISPLAYERID(int id) - { - return id != 0 && (id & 0x80000000) == 0; - } - public bool ISNPCID(int id) => ((id & 0x80000000) != 0) && ((id & 0x40000000) == 0); - public bool ISMATTERID(int id) => ((id) & 0xC0000000) == 0xC0000000; - private byte[] GetBytes(byte[] bytes, int length, int index) - { - byte[] arrByteData = new byte[length]; - for (int i = 0; i < length; i++) - { - arrByteData[i] = bytes[i + index]; - } - - return arrByteData; - } - - public void c2s_CmdPlayerMove(in Vector3 vCurPos, in Vector3 vDest, - int iTime, float fSpeed, int iMoveMode, ushort wStamp) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = - C2SCommandFactory.CreatePlayerMove(vCurPos, vDest, (ushort)iTime, fSpeed, (byte)iMoveMode, wStamp); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdCastSkill(int idSkill, byte byPVPMask, int iNumTarget, int[] aTargets) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = - C2SCommandFactory.CreatePlayerCastSkill(idSkill, byPVPMask, iNumTarget, aTargets); - - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdCastInstantSkill(int idSkill, byte byPVPMask, int iNumTarget, int[] aTargets) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = - C2SCommandFactory.CreatePlayerCastInstantSkill(idSkill, byPVPMask, iNumTarget, aTargets); - - SendProtocol(gamedatasend); - } - - public void c2s_CmdCastPosSkill(int idSkill, Vector3 vDest, byte byPVPMask, int iNumTarget, int aTargets) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = - C2SCommandFactory.CreatePlayerCastPosSkill(idSkill, vDest, byPVPMask, iNumTarget, aTargets); - - SendProtocol(gamedatasend); - } - public void c2s_SendCmdContinueAction() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.CONTINUE_ACTION); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdStopMove(in Vector3 vDest, float fSpeed, int iMoveMode, - byte byDir, ushort wStamp, int iTime) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = - C2SCommandFactory.CreatePlayerStop(vDest, fSpeed, (byte)iMoveMode, byDir, wStamp, (ushort)iTime); - SendProtocol(gamedatasend); - } - - public void c2s_CmdSendEnterPKPrecinctint() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.ENTER_PK_PROTECTED); - SendProtocol(gamedatasend); - } - public void SendChatData(byte cChannel, in string szMsg, int iPack, int iSlot) - { - publicchat publicChat = new publicchat(); - publicChat.Channel = cChannel; - publicChat.Roleid = m_iCharID; - - byte[] unicodeBytes = Encoding.Unicode.GetBytes(szMsg); - publicChat.Msg.Replace(unicodeBytes); - _logger.Log(LogType.Warning, $"HoangDev : publicChat {publicChat}"); - SendProtocol(publicChat); - } - public void LoadConfigData() - { - getuiconfig p = new getuiconfig(); - p.Roleid = m_iCharID; - SendProtocol(p); - } - private void SetCharacterID(int iCharID) - { - m_iCharID = iCharID; - } - - private void OnPrtcChatMessage(Protocol pProtocol, bool bCalledagain) - { - chatmessage p = (chatmessage)pProtocol; - - string strTemp = System.Text.Encoding.Unicode.GetString(p.Msg.ToArray(), 0, p.Msg.Length); - - _logger.Log(LogType.Warning, $"HoangDev : OnPrtcChatMessage :{strTemp}"); - EventBus.Publish(new ChatMessageEvent(strTemp)); - } - - public struct ChatMessageEvent - { - public string context; - - public ChatMessageEvent(string context) - { - this.context = context; - } - } - public void OnPrtcGetConfigRe(Protocol pProtocol) - { - getuiconfig_re p = (getuiconfig_re)pProtocol; - if (p.Result != (int)ErrCode.ERR_SUCCESS) - BMLogger.LogError("CECGameSession::OnPrtcGetConfigRe, link return error code of " + p.Result); - else - { - if (!CECGameRun.Instance.LoadConfigsFromServer(p.UiConfig.RawBuffer, p.UiConfig.Size)) - { - // if load failed then use current setting directly - //TODO : fix later - EC_Game.GetConfigs().ApplyUserSetting(); - } - - // 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. - // Enalbe game UI - CECGameUIMan pGameUI = (CECGameUIMan)EC_Game.GetGameRun().GetUIManager().GetInGameUIMan(); - if (pGameUI != null) - { - pGameUI.EnableUI(true); - - // Get referral name for adding friend or other display - //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);*/ - } - - CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer(); - pHost.OnAllInitDataReady(); - - /* if (pHost.IsGM()) - { - CDlgCountryMap pDlgCountryMap = (CDlgCountryMap)pGameUI.GetDialog("Win_CountryMap"); - pDlgCountryMap.GetConfig(); - } - - g_pGame.GetConfigs().ApplyOptimizeSetting(); - - if (g_pGame.GetConfigs().IsMiniClient()) - CECMCDownload::GetInstance().SendGetDownloadOK();*/ - } - } - private void OnPrtcPlayerBaseInfoRe(Protocol pProtocol) - { - playerbaseinfo_re p = (playerbaseinfo_re)pProtocol; - BMLogger.Log($"OnPrtcPlayerBaseInfoRe: {p.Roleid} {p.Player.cls} {p.Player.gender}"); - EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERBASEINFO, MANAGER_INDEX.MAN_PLAYER, -1, p); - } - - public void c2s_CmdNPCSevAcceptTask(int idTask, int idStorage, int idRefreshItem) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateCmdNPCSevAcceptTask(idTask, idStorage, idRefreshItem); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdGetAllData(bool byPack, bool byEquip, bool byTask) - { - gamedatasend gamedatasend = new gamedatasend(); - - gamedatasend.Data = C2SCommandFactory.CreateGetAllDataCommand(byPack, byEquip, byTask); - _logger.Log(LogType.Warning, $"[Dat]- SendCmdGetAllData {byPack},{byEquip},{byTask}"); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevHello(int nid) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevHelloDataCommand(nid); - SendProtocol(gamedatasend); - } - - public void c2s_CmdNormalAttack(byte byPVPMask) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNormalAttackDataCmd(byPVPMask); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdCancelAction() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.CANCEL_ACTION); - SendProtocol(gamedatasend); - } - - public void c2s_CmdUnselect() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.UNSELECT); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdSelectTarget(int idTarget) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateSelectTarget(idTarget); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevWaypoint() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevWaypointCmd(NPC_service_type.GP_NPCSEV_WAYPOINT, 0); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdNPCSevMakeItem(int idSkill, int idItem, uint dwCount) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevMakeItemCmd(idSkill, idItem, dwCount); - SendProtocol(gamedatasend); - } - public void GetRoleBaseInfo(int iNumRole, List aRoleIDs) - { - int iNumLimit = 128; - playerbaseinfo p = null; - int iCount = 0; - - while (iCount < iNumRole) - { - p = new(); - p.Roleid = _selectedRole.roleid; - - int iNumSend = iNumLimit; - if (iCount + iNumLimit > iNumRole) - iNumSend = iNumRole - iCount; - - if (iNumSend > 0) - { - p.playerList = new(); - for (int i = 0; i < iNumSend; i++) - p.playerList.Add(aRoleIDs[iCount + i]); - - SendProtocol(p); - } - - - iCount += iNumSend; - } - } - - public void c2s_SendCmdGetOtherEquip(int iNumID, List aIDs) - { - // int iNumLimit = 250; - // int iCount = 0; - - // while (iCount < iNumID) - // { - // int iNumSend = iNumLimit; - // if (iCount + iNumLimit > iNumID) - // iNumSend = iNumID - iCount; - - // if (iNumSend > 0) - // { - // } - // } - } - - public void c2s_SendCmdNPCSevAcceptTask(int idTask, int idStorage, int idRefreshItem) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateCmdNPCSevAcceptTask( - idTask, - idStorage, - idRefreshItem); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevReturnTask(int idTask, int iChoice) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevReturnTaskCmd( - idTask, - iChoice); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevTaskMatter(int idTask) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevTaskMatterCmd(idTask); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevLearnSkill(int idSkill) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevLearnSkillCmd(idSkill); - BMLogger.LogError("HoangDev : c2s_SendCmdNPCSevLearnSkill gamedatasend.Data : " + gamedatasend.Data.Size); - BMLogger.LogError("HoangDev : c2s_SendCmdNPCSevLearnSkill idSkill : " + idSkill); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevBuy(int itemNum, C2SCommand.npc_trade_item[] items) - { - if (itemNum <= 0 || items == null || items.Length < itemNum) - return; - - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevBuyCmd(itemNum, items); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdNPCSevSell(int itemNum, C2SCommand.npc_sell_item[] items) - { - if (itemNum <= 0 || items == null || items.Length < itemNum) - return; - - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevSellCmd(itemNum, items); - SendProtocol(gamedatasend); - } - - public void GetRoleCustomizeData(int iNumRole, List aRoleIDs) - { - if (iNumRole <= 0 || aRoleIDs == null || aRoleIDs.Count == 0) return; - - int iNumLimit = 240; - int iCount = 0; - - while (iCount < iNumRole) - { - getcustomdata p = new(); - p.Roleid = _selectedRole.roleid; - - int iNumSend = iNumLimit; - if (iCount + iNumLimit > iNumRole) - iNumSend = iNumRole - iCount; - - for (int i = 0; i < iNumSend; i++) - p.playerlist.Add(aRoleIDs[iCount + i]); - - SendProtocol(p); - - iCount += iNumSend; - } - } - public void c2s_SendCmdEmoteAction(uint wPose) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateEmoteActionCmd((int)wPose); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdTaskNotify(byte[] pData, uint dwDataSize) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateTaskNotifyCmd( pData, dwDataSize); - BMLogger.Log($"[MH Task] c2s_SendCmdTaskNotify Command ID : {pData[0]} Size: {dwDataSize}"); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdStandUp() - { - gamedatasend gamedatasend = new gamedatasend(); - 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(); - gamedatasend.Data = C2SCommandFactory.CreateAutoTeamSetGoalCommand(type,goal_id, op); - SendProtocol(gamedatasend); - } - - public void c2s_CmdGoto(float x, float y, float z) - { - c2s_SendCmdGoto(x, y, z); - } - - // Send C2S::GOTO command data - void c2s_SendCmdGoto(float x, float y, float z) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateGoToCommed( x, y, z); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdUseItem(byte byPackage, byte bySlot, int tid, byte byCount) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateUseItemCmd(byPackage, bySlot, tid, byCount); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdGetExtProps() - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.GET_EXT_PROP); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdGivePresent(int roleid, int mail_id, int goods_id, int goods_index, int goods_slot) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateGivePresentCmd(roleid, mail_id, goods_id, goods_index, goods_slot); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdEnterSanctuary(int id) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateEnterSanctuaryCmd(id); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdEnterInstance(int iTransIdx, int idInst) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateEnterInstanceCmd(iTransIdx, idInst); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdActiveRushFly(bool bActive) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateActiveRushFlyCmd(bActive); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdPetCtrl(int idTarget, int cmd, byte[] pParamBuf, int iParamLen) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreatePetCtrlCmd(idTarget, cmd, pParamBuf, iParamLen); - SendProtocol(gamedatasend); - } - - public void c2s_SendCmdQueryFactionPVPInfo(int faction_id) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateQueryFactionPVPInfo(faction_id); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdNPCSevEmbed(ushort wStoneIdx, ushort wEquipIdx, int tidStone, int tidEquip) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateNPCSevEmbedCmd(wStoneIdx, wEquipIdx, tidStone, tidEquip); - SendProtocol(gamedatasend); - } - public void c2s_SendCmdGetItemInfo(byte byPackage, int bySlot) - { - gamedatasend gamedatasend = new gamedatasend(); - gamedatasend.Data = C2SCommandFactory.CreateGetItemInfoCmd(byPackage, bySlot); - SendProtocol(gamedatasend); - } - } +using BrewMonster; +using BrewMonster.Common; +using BrewMonster.Managers; +using BrewMonster.Network; +using BrewMonster.Scripts.Skills; +using BrewMonster.UI; +using CSNetwork.C2SCommand; +using CSNetwork.GPDataType; +using CSNetwork.Protocols; +using CSNetwork.Protocols.RPCData; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; +using CommandID = CSNetwork.GPDataType.CommandID; + +namespace CSNetwork +{ + public class GameSession : IDisposable + { + private static IPrefixedLogger + _logger = LoggerFactory.GetLogger(nameof(GameSession)); // Get class-specific logger + + private NetworkManager _networkManager; + private string _host; + private int _port; + private string _username; + private string _password; + private int _currentUserId = -1; // To store the UserID after successful login + private uint _localsid = 0; // To store the LocalSID from onlineannounce + private int m_iCharID; + private int m_idLastSelTarget = 0; // ID of selected item last time + + CECStringTab m_ErrorMsgs; + + // State management for async operations and callbacks + private Action _loginCallback; + private Action> _roleListCallback; + private List _accumulatedRoles; + private Action _selectRoleCallback; + private Action _createRoleCallback; + private RoleInfo _selectedRole; + public bool IsConnected => _networkManager?.IsConnected ?? false; + public static SynchronizationContext Context; + private CECC2SCmdCache m_CmdCache; // C2S command cache + + private static void PostToUnityContext(Action action) + { + if (action == null) return; + var ctx = Context; + if (ctx != null) + { + ctx.Post(_ => action(), null); + } + else + { + action(); + } + } +#if UNITY_EDITOR + public bool isDebug; + public bool IsDebug + { + get => isDebug; + set => isDebug = value; + } +#endif + public CECC2SCmdCache CmdCache { get => m_CmdCache; } + + + public GameSession() + { + _networkManager = new NetworkManager(); + m_CmdCache = new CECC2SCmdCache(); + _networkManager.ProtocolReceived += OnProtocolReceived; + _networkManager.ErrorOccurred += OnErrorOccurred; + _networkManager.Disconnected += OnDisconnected; + } + + public void SetLogPath(string path) + { + LoggerFactory.SetFileLoggerImplementation(new FileLogger()); + _logger = LoggerFactory.GetCustomLogger(path, nameof(GameSession) + GetHashCode(), LoggerType.File); + _networkManager.SetLogPath(path); + } + + /// + /// Connects to the game server asynchronously. + /// + /// Server hostname or IP address. + /// Server port. + /// Task representing the asynchronous connect operation. Check IsConnected property or handle Disconnected event for status. + public async Task ConnectAsync(string host, int port) + { + if (IsConnected) + { + _logger.Log(LogType.Warning, "ConnectAsync called but already connected."); + return; + } + + _host = host; + _port = port; + _logger.Log(LogType.Info, $"Attempting to connect to {_host}:{_port}..."); + try + { + await _networkManager.ConnectAsync(_host, _port); + if (IsConnected) + { + _logger.Log(LogType.Info, "Connection established."); + } + else + { + _logger.Log(LogType.Warning, + "Connection failed after ConnectAsync completed (check NetworkManager logs/events)."); + } + } + catch (Exception ex) + { + _logger.Log(LogType.Error, $"Connection exception: {ex.Message}"); + _logger.LogException(ex); + OnDisconnected(); + } + } + + + public void Disconnect() + { + _networkManager.Disconnect(); + } + + /// + /// Initiates the login process asynchronously. + /// + /// Account username. + /// Account password. + /// Action invoked with true on successful login (OnlineAnnounce received), false otherwise. + public void LoginAsync(string username, string password, Action callback) + { + if (!IsConnected) + { + _logger.Log(LogType.Warning, "LoginAsync called but not connected."); + callback?.Invoke(false); + return; + } + + if (_loginCallback != null) + { + _logger.Log(LogType.Warning, "LoginAsync called while another login is already in progress."); + callback?.Invoke(false); + return; + } + + _username = username; + _password = password; + _loginCallback = callback; + _currentUserId = -1; // Reset user ID + + _logger.Log(LogType.Info, $"Initiating login for user '{_username}'..."); + } + + /// + /// Initiates fetching the role list asynchronously. Requires successful login. + /// + /// Action invoked with the complete list of roles, or null/empty list on failure. + public void GetRoleListAsync(Action> callback) + { + if (!IsConnected) + { + _logger.Log(LogType.Warning, "GetRoleListAsync called but not connected."); + callback?.Invoke(null); + return; + } + + if (_currentUserId == -1) + { + _logger.Log(LogType.Warning, "GetRoleListAsync called but not logged in."); + callback?.Invoke(null); + return; + } + + if (_roleListCallback != null) + { + _logger.Log(LogType.Warning, + "GetRoleListAsync called while another role list retrieval is already in progress."); + callback?.Invoke(null); + return; + } + + _roleListCallback = callback; + _accumulatedRoles = new List(); + _logger.Log(LogType.Info, "Requesting role list..."); + RequestRoleListInternal(); + } + + public RoleInfo GetRoleInfo() + { + return _selectedRole; + } + + public void SelectRoleAsync(RoleInfo role, Action callback) + { + _selectedRole = role; + _selectRoleCallback = callback; + SetCharacterID(role.roleid); + SendProtocol(new selectrole() + { + Roleid = role.roleid, + Flag = 0 + }); + } + + public void CreateRoleAsync(RoleInfo roleInfo, Octets referId, Action callback) + { + if (!IsConnected) + { + callback?.Invoke(null); + return; + } + + if (_currentUserId == -1) + { + callback?.Invoke(null); + return; + } + + if (_createRoleCallback != null) + { + callback?.Invoke(null); + return; + } + + _createRoleCallback = callback; + + createrole createRoleProtocol = new createrole() + { + Userid = _currentUserId, + Localsid = _localsid, + Roleinfo = roleInfo, + Referid = referId ?? new Octets() + }; + + 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) + { + byte[] customDataPreview = new byte[Math.Min(16, (int)roleInfo.custom_data.Size)]; + Array.Copy(roleInfo.custom_data.ByteArray, 0, customDataPreview, 0, customDataPreview.Length); + 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); + } + + /// + /// Helper method to create a new RoleInfo for character creation. + /// Matches C++ NewCharacterImpl behavior. + /// + public static RoleInfo CreateNewRoleInfo(string name, int profession, int gender) + { + RoleInfo roleInfo = new RoleInfo(); + + // Set basic info + roleInfo.occupation = (byte)profession; + roleInfo.gender = (byte)gender; + roleInfo.level = 1; + roleInfo.level2 = 0; + roleInfo.status = 0; // _ROLE_STATUS_NORMAL + roleInfo.delete_time = 0; + roleInfo.create_time = 0; // Server will set this + roleInfo.lastlogin_time = 0; + roleInfo.posx = 0.0f; + roleInfo.posy = 0.0f; + roleInfo.posz = 0.0f; + roleInfo.worldtag = 0; // Server will set this + roleInfo.referrer_role = 0; + roleInfo.cash_add = 0; + + // Set name - C++ uses Unicode encoding (ACHAR = wchar_t) + if (!string.IsNullOrEmpty(name)) + { + byte[] nameBytes = Encoding.Unicode.GetBytes(name); + roleInfo.name = new Octets(nameBytes); + } + else + { + roleInfo.name = new Octets(); + } + + // Initialize equipment list with 29 empty items (IVTRSIZE_EQUIPPACK = 29) + roleInfo.equipment = new List(); + for (int i = 0; i < 29; i++) + { + // Important: GRoleInventory.data must be non-null or Marshal() will fail and the packet won't send. + roleInfo.equipment.Add(new GRoleInventory + { + id = 0, + pos = i, + count = 0, + max_count = 0, + data = new Octets(), + proctype = 0, + expire_date = 0, + guid1 = 0, + guid2 = 0, + mask = 0 + }); + } + + // 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(); + roleInfo.reincarnation_data = new Octets(); + roleInfo.realm_data = new Octets(); + + // Race is typically determined by profession, but we'll leave it at 0 for now + // Server may set it based on profession + roleInfo.race = 0; + + return roleInfo; + } + + /// + /// Creates default customize data exactly matching C++ LoadDefaultCustomizeData behavior. + /// Structure layout - trying multiple sizes due to potential padding: + /// - DWORD dwVersion (offset 0-3, 4 bytes) = 0x10007001 + /// - FACE_CUSTOMIZEDATA faceData (offset 4-87, 84 bytes) = all zeros + /// - unsigned short bodyID (offset 88-89, 2 bytes) = 0 + /// - A3DCOLOR colorBody (offset 90-93, 4 bytes) = 0xffffffff + /// - 6 unsigned char scales (offset 94-99, 6 bytes) = all 128 + /// Note: C++ sizeof might include padding, trying 100, 104, 108 bytes + /// + private static Octets CreateDefaultCustomizeData(int profession, int gender) + { + // C++ sizeof(PLAYER_CUSTOMIZEDATA) - Based on server response, it expects 176 bytes + // The C++ code sends: sizeof(CECPlayer::PLAYER_CUSTOMIZEDATA) which appears to be 176 bytes + // Server response shows: custom_data size=176, so server expects/receives 176 bytes + // Structure layout (176 bytes total): + // - DWORD dwVersion (4 bytes, offset 0) = 0x10007001 + // - FACE_CUSTOMIZEDATA faceData (84 bytes, offset 4) = all zeros for now + // - unsigned short bodyID (2 bytes, offset 88) = 0 + // - A3DCOLOR colorBody (4 bytes, offset 90) = 0xffffffff + // - 6 unsigned char scales (6 bytes, offset 94) = all 128 + // - Padding/additional fields (76 bytes, offset 100-175) = all zeros + 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 + // 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); + } + + public void EnterWorldAsync(RoleInfo role, Action callback) + { + SendProtocol(new enterworld() + { + Roleid = _selectedRole.roleid, + Provider_link_id = 0, + }, callback); + } + + public void RequestDropIvtrItem(byte index, int amount) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.CreateDropIvtrItem(index, amount); + SendProtocol(gamedatasendRequest); + } + + public void RequestDropEquipItem(byte index) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.CreateDropEquipItem(index); + SendProtocol(gamedatasendRequest); + } + + public void RequestPickupItem(int idItem, int tid) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.CreatePickupItem(idItem, tid); + SendProtocol(gamedatasendRequest); + } + + public void c2s_SendCmdGetIvtrDetailData(byte byPackage, Action callback) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdGetIvtrDetailData(byPackage); + SendProtocol(gamedatasendRequest, callback); + } + + public void c2s_SendCmdQueryCashInfo() + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdQueryCashInfo(); + SendProtocol(gamedatasendRequest); + } + + public void c2s_SendCmdOpenFashionTrash(string password) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdOpenFashionTrash(password); + SendProtocol(gamedatasendRequest); + } + + public void c2s_SendCmdEquipItem(byte iIvtrIdx, byte iEquipIdx, Action callback) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdEquipItem(iIvtrIdx, iEquipIdx); + SendProtocol(gamedatasendRequest, callback); + } + + public void c2s_SendCmdReviveVillage(int param = 0) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); + SendProtocol(gamedatasendRequest); + } + public void c2s_SendCmdReviveItem(int param = 0) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); + SendProtocol(gamedatasendRequest); + } + public void RequestReviveByPlayer(int param = 0) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.c2s_SendCmdReviveVillage(param); + SendProtocol(gamedatasendRequest); + } + + + public void c2s_SendCmdMallShopping(uint count, CMD_MallShopping.goods[] goodsArray) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.CreateGetMallShopping(count, goodsArray); + SendProtocol(gamedatasendRequest); + } + public void c2s_SendCmdGatherMaterial(int idMatter, int iToolPack, int idToolIndex, int idTool, int idTask) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = CSNetwork.C2SCommand.C2SCommandFactory.c2s_SendCmdGatherMaterial(idMatter, iToolPack, idToolIndex, idTool, idTask); + SendProtocol(gamedatasendRequest); + } + + public void RequestOwnItemInfoAsync( + byte byPackage, + byte bySlot, + int type, + int expire_date, + int state, + uint count, + ushort crc, + ushort content_length, + byte[] content, + Action callback) + { + gamedatasend gamedatasendRequest = new gamedatasend(); + gamedatasendRequest.Data = C2SCommandFactory.CreateOwnItemInfo(byPackage, bySlot, type, expire_date, state, + count, crc, content_length, content); + SendProtocol(gamedatasendRequest, callback); + } + + // --- Protocol Sending --- + public void SendProtocol(Protocol protocol, Action complete = null) + { + if (IsConnected) + { + _logger.Log(LogType.Debug, + $"Sending protocol: {protocol.GetType().Name} (Detail: {protocol.ToString})"); + Debug.Log($"[GameSession] Sending protocol: {protocol.GetType().Name} (Type: {protocol.GetPType()})"); + _networkManager.Send(protocol); + complete?.Invoke(); + } + else + { + _logger.Log(LogType.Warning, $"Cannot send protocol ({protocol.GetType().Name}), not connected."); + Debug.LogError($"[GameSession] Cannot send protocol ({protocol.GetType().Name}), not connected."); + } + } + + // --- Event Handlers (from NetworkManager) --- + + private void OnProtocolReceived(Protocol protocol) + { + _logger.Log(LogType.Debug, $"Received protocol: {protocol.GetType().Name} (Type: {protocol.Type})"); + if (protocol is null) + return; + + + // Route protocol to appropriate handler + switch (protocol.GetPType()) + { + case ProtocolType.PROTOCOL_CHALLENGE: + HandleChallenge((challenge)protocol); + break; + case ProtocolType.PROTOCOL_KEYEXCHANGE: + HandleKeyExchange((KeyExchange)protocol); + break; + case ProtocolType.PROTOCOL_ONLINEANNOUNCE: + HandleOnlineAnnounce((onlineannounce)protocol); + break; + case ProtocolType.PROTOCOL_ROLELIST_RE: + HandleRoleListResponse((RoleListResponse)protocol); + break; + // Add cases for other protocols GameSession might need to handle + case ProtocolType.PROTOCOL_SELECTROLE_RE: + HandleSelectRoleResponse((SelectRole_Re)protocol); + //_networkManager.IgnoreBytes = 2; + break; + case ProtocolType.PROTOCOL_CREATEROLE_RE: + HandleCreateRoleResponse((createrole_re)protocol); + break; + case ProtocolType.PROTOCOL_ERRORINFO: + HandleErrorInfo((errorinfo)protocol); + break; + case ProtocolType.PROTOCOL_S2CGAMEDATASEND: + case ProtocolType.PROTOCOL_GAMEDATASEND: + HandleServerDataSend((gamedatasend)protocol); + break; + case ProtocolType.PROTOCOL_CHATMESSAGE: + _logger.Log(LogType.Warning, $"HoangDev :ProtocolType.PROTOCOL_CHATMESSAGE {protocol.GetPType()}"); + OnPrtcChatMessage(protocol, false); + break; + case ProtocolType.PROTOCOL_PLAYERBASEINFO_RE: + OnPrtcPlayerBaseInfoRe(protocol); + break; + case ProtocolType.PROTOCOL_GETUICONFIG_RE: OnPrtcGetConfigRe(protocol); break; + + case ProtocolType.PROTOCOL_AUTOTEAMSETGOAL_RE: + { + // CECAutoTeam pAutoTeam = CECGameRun.Instance.GetHostPlayer().GetAutoTeam(); + // if( pAutoTeam !=null) + // pAutoTeam.OnPrtcAutoTeamSetGoalRe((AutoTeamSetGoal_Re)protocol); + } + break; + + default: + _logger.Log(LogType.Warning, $"Received unhandled protocol type: {protocol.GetPType()}"); + break; + } + } + + private void HandleServerDataSend(gamedatasend protocol) + { + int lenghtHeader = Marshal.SizeOf(); + var pDataBuf = new byte[protocol.Data.ByteArray.Length - lenghtHeader]; + var byteArrHeader = new byte[lenghtHeader]; + long dwDataSize = protocol.Data.Size; + + if (dwDataSize < Marshal.SizeOf()) + { + _logger.Error($"### GameDataSend: size invalid {dwDataSize}"); + return; + } + + dwDataSize -= Marshal.SizeOf(); // subtract the header size (ushort) + for (int i = 0; i < protocol.Data.ByteArray.Length; i++) + { + if (i < lenghtHeader) + { + byteArrHeader[i] = protocol.Data.ByteArray[i]; + } + else + { + pDataBuf[i - lenghtHeader] = protocol.Data.ByteArray[i]; + } + } + + var pCmdHeader = BitConverter.ToUInt16(byteArrHeader); + //sss +#if UNITY_EDITOR + if (isDebug) + { + BMLogger.LogError($"### GameDataSend: CMDID {pCmdHeader}"); + } +#endif + int iHostID = _selectedRole.roleid; + switch (pCmdHeader) + { + case CommandID.PLAYER_INFO_2: + case CommandID.PLAYER_INFO_3: + case CommandID.PLAYER_INFO_4: + case CommandID.PLAYER_INFO_2_LIST: + case CommandID.PLAYER_INFO_3_LIST: + case CommandID.PLAYER_INFO_23_LIST: + + break; + + case CommandID.PLAYER_INFO_1: + case CommandID.PLAYER_ENTER_WORLD: + case CommandID.PLAYER_ENTER_SLICE: + case CommandID.PLAYER_INFO_1_LIST: + case CommandID.PLAYER_INFO_00: + case CommandID.SELF_INFO_1: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERINFO, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, + pCmdHeader, iHostID); + break; + case CommandID.OBJECT_MOVE: + int lenghtDataType = Marshal.SizeOf(); + byte[] arrByteData = GetBytes(pDataBuf, lenghtDataType, 0); + int idObjMove = BitConverter.ToInt32(arrByteData); + if (ISPLAYERID(idObjMove)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERMOVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, + pDataBuf, pCmdHeader, iHostID); + } + else if (ISNPCID(idObjMove)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCMOVE, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, + pCmdHeader); + } + + break; + case CommandID.OBJECT_STOP_MOVE: + { + int id1 = GPDataTypeHelper.FromBytes(pDataBuf); + + if (ISPLAYERID(id1)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERSTOPMOVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, + pDataBuf, pCmdHeader); + } + else if (ISNPCID(id1)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCSTOPMOVE, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, + pCmdHeader); + } + + break; + } + case CommandID.OBJECT_LEAVE_SLICE: + { + int id = GPDataTypeHelper.FromBytes(pDataBuf); + if (ISPLAYERID(id)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERRUNOUT, (int)MANAGER_INDEX.MAN_PLAYER, -1, + pDataBuf, pCmdHeader); + } + else if (ISNPCID(id)) + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCRUNOUT, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, + pCmdHeader); + } + + break; + } + case CommandID.OWN_IVTR_DATA: + case CommandID.OWN_IVTR_DETAIL_DATA: + case CommandID.GET_OWN_MONEY: + case CommandID.CHANGE_IVTR_SIZE: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader, iHostID); + break; + case CommandID.EXG_IVTR_ITEM: + case CommandID.MOVE_IVTR_ITEM: + case CommandID.PLAYER_DROP_ITEM: + case CommandID.EXG_EQUIP_ITEM: + case CommandID.EQUIP_ITEM: + case CommandID.MOVE_EQUIP_ITEM: + case CommandID.UNFREEZE_IVTR_SLOT: + case CommandID.PLAYER_EQUIP_TRASHBOX_ITEM: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ITEMOPERATION, (int)MANAGER_INDEX.MAN_PLAYER, 0, + pDataBuf, pCmdHeader); + break; + case CommandID.PLAYER_CASH: + { + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_IVTRINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader, iHostID); + break; + } + case CommandID.MATTER_INFO_LIST: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERINFO, (int)MANAGER_INDEX.MAN_MATTER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.MATTER_ENTER_WORLD: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERENTWORLD, (int)MANAGER_INDEX.MAN_MATTER, 0, + pDataBuf, pCmdHeader); + break; + case CommandID.PICKUP_ITEM: + case CommandID.HOST_OBTAIN_ITEM: + case CommandID.PRODUCE_ONCE: + case CommandID.TASK_DELIVER_ITEM: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PICKUPITEM, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.MATTER_PICKUP: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PICKUPMATTER, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.PICKUP_MONEY: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PICKUPMONEY, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.HOST_CORRECT_POS: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CORRECTPOS, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader, iHostID); + break; + case CommandID.OWN_ITEM_INFO: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_OWNITEMINFO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader, iHostID); + break; + case CommandID.PLAYER_DIED: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDIED, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.HOST_DIED: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_DIED, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.PLAYER_REVIVE: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERREVIVE, (int)MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.NOTIFY_HOSTPOS: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_GOTO, (int)MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.NPC_ENTER_SLICE: + case CommandID.NPC_INFO_LIST: + case CommandID.NPC_INFO_00: + case CommandID.NPC_ENTER_WORLD: + case CommandID.NPC_VISIBLE_TID_NOTIFY: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCINFO, (int)MANAGER_INDEX.MAN_NPC, 0, pDataBuf, + pCmdHeader, dwDataSize); + break; + case CommandID.TASK_DATA: + case CommandID.TASK_VAR_DATA: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TASKDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader, dwDataSize); + break; + case CommandID.BE_HURT: + case CommandID.HURT_RESULT: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_HURTRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.OBJECT_ATTACK_RESULT: + //int id = GPDataTypeHelper.FromBytes(pDataBuf); + cmd_object_atk_result pCmdAtk = GPDataTypeHelper.FromBytes(pDataBuf); + //BMLogger.LogError($"OBJECT_ATTACK_RESULT: npc ? " + ISNPCID(id)); + + if (ISPLAYERID(pCmdAtk.attacker_id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERATKRESULT, MANAGER_INDEX.MAN_PLAYER, -1, + pDataBuf, pCmdHeader); + else if (ISNPCID(pCmdAtk.attacker_id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCATKRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + break; + case CommandID.HOST_ATTACKRESULT: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ATKRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.HOST_ATTACKED: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_ATTACKED, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + + case CommandID.ERROR_MESSAGE: + { + int errRaw = BitConverter.ToInt32(pDataBuf, 0); + // Note: _logger may be configured as a file logger via SetLogPath(), so also log to console for visibility. + _logger.Info($"### GameDataSend: ERROR_MESSAGE: {errRaw}"); +#if UNITY_EDITOR + BMLogger.LogError($"### GameDataSend: ERROR_MESSAGE: {errRaw}"); +#endif + cmd_error_msg pCmd = GPDataTypeHelper.FromBytes(pDataBuf); +#if UNITY_EDITOR + BMLogger.LogError($"### GameDataSend: ERROR_MESSAGE parsed iMessage={pCmd.iMessage}"); +#endif + + if (pCmd.iMessage != 0) + { + // string szMsg = m_ErrorMsgs.GetWideString(pCmd.iMessage); + // if (string.IsNullOrEmpty(szMsg)) + // BMLogger.LogError("SERVER - unknown error !"); + //else if (pCmd.iMessage != 2) + //g_pGame.GetGameRun().AddChatMessage(szMsg, GP_CHAT_MISC); + } + + if (pCmd.iMessage == 2) + { + // Attack target is too far + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_TARGETISFAR, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + } + else if (pCmd.iMessage == 20) + { + // Failed to cast skill + //pGameRun.PostMessage(MSG_PM_CASTSKILL, MAN_PLAYER, 0, (DWORD)pDataBuf, pCmdHeader.cmd); + } + else if (pCmd.iMessage == 133 || pCmd.iMessage == 134) + { + // deal failed + //pGameRun.PostMessage(MSG_HST_BUY_SELL_FAIL, MAN_PLAYER, 0, (DWORD)pDataBuf, pCmdHeader.cmd); + } + else if (pCmd.iMessage == 158) + { + // µ±Ç°»ãÂʲ»¶Ô£¬ÖØÐÂÈ¡»ãÂÊ + //c2s_CmdGetCashMoneyRate(); + } + else if (pCmd.iMessage == 108 /*&& pGameRun.GetHostPlayer().IsInKingService()*/) + { + /* CECGameUIMan* pGameUI = pGameRun.GetUIManager().GetInGameUIMan(); + if (pGameUI) + pGameUI.EndNPCService();*/ + } + else if + (pCmd.iMessage == 108 /*&& pGameRun.GetHostPlayer().GetOfflineShopCtrl().GetNPCSevFlag() != COfflineShopCtrl::NPCSEV_NULL*/ + ) + { + /* CECGameUIMan* pGameUI = pGameRun.GetUIManager().GetInGameUIMan(); + if (pGameUI) + pGameUI.EndNPCService();*/ + } + else if (pCmd.iMessage == 175) + { + //c2s_CmdQueryParallelWorld(); + } + else if (pCmd.iMessage == 6) + { + //AP_ActionEvent(AP_EVENT_CANNOTPICKUP); + } + + break; + } + case CommandID.SELECT_TARGET: + case CommandID.UNSELECT: + + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SELTARGET, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.NPC_DIED: + case CommandID.NPC_DIED2: + + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCDIED, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + break; + case CommandID.OBJECT_DISAPPEAR: + { + int lenghtDataType1 = Marshal.SizeOf(); + byte[] arrByteData1 = GetBytes(pDataBuf, lenghtDataType1, 0); + int objectId = BitConverter.ToInt32(arrByteData1); + if (ISPLAYERID(objectId)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDISAPPEAR, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + else if (ISNPCID(objectId)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCDISAPPEAR, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + else if (ISMATTERID(objectId)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTERDISAPPEAR, MANAGER_INDEX.MAN_MATTER, 0, pDataBuf, pCmdHeader); + + break; + } + case CommandID.SELF_INFO_00: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_INFO00, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, + pCmdHeader); + break; + case CommandID.NPC_GREETING: + { + // If this greeting is from the skill-learn NPC, record it (C++ skill dialog relies on this). + try + { + cmd_npc_greeting greet = GPDataTypeHelper.FromBytes(pDataBuf); + CECHostSkillModel.Instance.OnNpcGreeting(greet.idObject); + } + catch (Exception ex) + { + _logger.Log(LogType.Warning, $"Failed to parse NPC_GREETING payload: {ex.Message}"); + } + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_NPCGREETING, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + } + case CommandID.ACTIVATE_WAYPOINT: + case CommandID.WAYPOINT_LIST: + + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_WAYPOINT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.SERVER_TIME: + { + cmd_server_time pcmd_server_time = GPDataTypeHelper.FromBytes(pDataBuf); + EC_ManMessage.PostMessage(EC_MsgDef.MSG_SERVERTIME, -1, 0, pcmd_server_time.time, pcmd_server_time.timebias); + break; + } + case CommandID.SCENE_SERVICE_NPC_LIST: + { + CECHostSkillModel.Instance.RecvNPCServiceList(protocol.Data); + break; + } + case CommandID.SKILL_DATA: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.OBJECT_CAST_SKILL: + case CommandID.OBJECT_CAST_INSTANT_SKILL: + case CommandID.OBJECT_CAST_POS_SKILL: + { + cmd_object_cast_skill pCmd2 = GPDataTypeHelper.FromBytes(pDataBuf,true); + if (ISPLAYERID(pCmd2.caster)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_CASTSKILL, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + else if (ISNPCID(pCmd2.caster)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCCASTSKILL, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + + break; + } + case CommandID.LEVEL_UP: + { + cmd_level_up pCmdLevelUp = GPDataTypeHelper.FromBytes(pDataBuf); ; + if (ISPLAYERID(pCmdLevelUp.id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERLEVELUP, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + else if (ISNPCID(pCmdLevelUp.id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCLEVELUP, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + break; + } + case CommandID.HOST_START_ATTACK: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_STARTATTACK, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); + break; + case CommandID.HOST_STOPATTACK: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_STOPATTACK, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); + break; + case CommandID.HOST_SKILL_ATTACK_RESULT: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLRESULT, 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); + break; + case CommandID.ENCHANT_RESULT: + cmd_enchant_result pCmd3 = GPDataTypeHelper.FromBytes(pDataBuf); + if (ISPLAYERID(pCmd3.caster)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_ENCHANTRESULT, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + else if (ISNPCID(pCmd3.caster)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_ENCHANTRESULT, MANAGER_INDEX.MAN_NPC, 0, pDataBuf, pCmdHeader); + break; + case CommandID.SKILL_PERFORM: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_CASTSKILL, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.SET_COOLDOWN: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SETCOOLTIME, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.COMBO_SKILL_PREPARE: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_COMBO_SKILL_PREPARE, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize); + break; + case CommandID.PLAYER_EXT_PROP_BASE: + case CommandID.PLAYER_EXT_PROP_MOVE: + case CommandID.PLAYER_EXT_PROP_ATK: + case CommandID.PLAYER_EXT_PROP_DEF: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEREXTPROP, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.OWN_EXT_PROP: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_OWNEXTPROP, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.OBJECT_DO_EMOTE: + case CommandID.OBJECT_EMOTE_RESTORE: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERDOEMOTE, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.OUT_OF_SIGHT_LIST: + { + cmd_out_of_sight_list pCmd5 = default; + pCmd5.uCount = GPDataTypeHelper.FromBytes(pDataBuf); + int offset2 = sizeof(uint); + pCmd5.idList = new int[pCmd5.uCount]; + for (int i = 0; i < pCmd5.uCount; i++) + { + pCmd5.idList[i] = GPDataTypeHelper.FromBytes(pDataBuf, offset2); + offset2 += 4;//sizeof int; + } + + for (uint n = 0; n < pCmd5.uCount; n++) + { + if (ISPLAYERID(pCmd5.idList[n])) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYEROUTOFVIEW, MANAGER_INDEX.MAN_PLAYER, -1, pCmd5.idList[n], pCmdHeader); + else if (ISNPCID(pCmd5.idList[n])) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_NM_NPCOUTOFVIEW, MANAGER_INDEX.MAN_NPC, 0, pCmd5.idList[n], pCmdHeader); + else if (ISMATTERID(pCmd5.idList[n])) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_MM_MATTEROUTOFVIEW, MANAGER_INDEX.MAN_MATTER, 0, pCmd5.idList[n], pCmdHeader); + } + + break; + } + case CommandID.PLAYER_GATHER_START: + case CommandID.PLAYER_GATHER_STOP: + case CommandID.MINE_GATHERED: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERGATHER, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + case CommandID.COOLTIME_DATA: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_COOLTIMEDATA, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.OBJECT_TAKEOFF: + { + cmd_object_takeoff pCmdTakeOff = GPDataTypeHelper.FromBytes((byte[])pDataBuf); + if (ISPLAYERID(pCmdTakeOff.object_id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + } + case CommandID.OBJECT_LANDING: + { + cmd_object_landing pCmdLanding = GPDataTypeHelper.FromBytes((byte[])pDataBuf); + if (ISPLAYERID(pCmdLanding.object_id)) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader); + break; + } + case CommandID.HOST_RUSH_FLY: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERFLY, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.FLYSWORD_TIME: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_FLYSWORDTIME, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.PRODUCE_START: + case CommandID.PRODUCE_END: + case CommandID.PRODUCE_NULL: + // Post MSG_HST_PRODUCEITEM message with command ID as parameter (matches C++ behavior) + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_PRODUCEITEM, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + + + case CommandID.LEARN_SKILL: + BMLogger.LogError("### GameDataSend: LEARN_SKILL"); + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_LEARNSKILL, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + case CommandID.EMBED_ITEM: + EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_EMBEDITEM, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader); + break; + default: +#if UNITY_EDITOR + if (isDebug) + { + BMLogger.LogError($"### GameDataSend: Unhandled CMDID {pCmdHeader} (payloadBytes={pDataBuf?.Length ?? 0})"); + } +#endif + break; + } + } + + + private void HandleSelectRoleResponse(SelectRole_Re protocol) + { + _logger.Info($"Select role response {protocol.result}"); + var callback = _selectRoleCallback; + PostToUnityContext(() => callback?.Invoke(_selectedRole)); + } + + 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})"; + _logger.Log(LogType.Error, errorMsg); + Debug.LogError($"[GameSession] {errorMsg}"); + var callback = _createRoleCallback; + _createRoleCallback = null; + PostToUnityContext(() => callback?.Invoke(null)); + return; + } + + Debug.Log($"[GameSession] Create role successful! RoleID: {protocol.roleid}"); + var successCallback = _createRoleCallback; + _createRoleCallback = null; + PostToUnityContext(() => successCallback?.Invoke(protocol.roleinfo)); + } + + 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) + { + Debug.LogError($"[GameSession] Create role failed due to server error: {protocol.Errcode}"); + var callback = _createRoleCallback; + _createRoleCallback = null; + PostToUnityContext(() => callback?.Invoke(null)); + } + } + + private void OnErrorOccurred(string errorMessage) + { + _logger.Log(LogType.Error, $"Network Error: {errorMessage}"); + FailLoginInProgress(errorMessage); + FailRoleListInProgress(errorMessage); + } + + private void OnDisconnected() + { + _logger.Log(LogType.Info, "Disconnected from server."); + _currentUserId = -1; + FailLoginInProgress("Disconnected"); + FailRoleListInProgress("Disconnected"); + // Clear command cache + m_CmdCache.RemoveAllCmds(); + } + + // --- Protocol Handling Logic --- + + private void HandleChallenge(challenge challenge) + { + if (_loginCallback == null || string.IsNullOrEmpty(_username)) + { + _logger.Log(LogType.Warning, "Received Challenge but not expecting it or username not set."); + return; + } + + _logger.Log(LogType.Info, "Handling Challenge..."); + + response response = new response(); + byte[] usernameBytes = Encoding.ASCII.GetBytes(_username); + byte[] passwordBytes = Encoding.ASCII.GetBytes(_password); + response.identity.Replace(usernameBytes); + response.Setup(new Octets(usernameBytes), new Octets(passwordBytes), challenge.nonce); + + uint clientId = 0xffffffff; + byte[] clientIdBytes = BitConverter.GetBytes(clientId); + response.cli_fingerprint.Replace(clientIdBytes); + response.use_token = 0; + + _networkManager.SetNonce(response.response_data); + SendProtocol(response); + _logger.Log(LogType.Info, "Sent Response."); + } + + private void HandleKeyExchange(KeyExchange keyExchange) + { + if (_loginCallback == null || string.IsNullOrEmpty(_username)) + { + _logger.Log(LogType.Warning, "Received KeyExchange but not expecting it."); + return; + } + + _logger.Log(LogType.Info, "Handling KeyExchange..."); + keyExchange.Setup(_networkManager, _username); + keyExchange.Blkickuser = 1; + SendProtocol(keyExchange); + _logger.Log(LogType.Info, "Sent KeyExchange acknowledgment/response."); + } + + private void HandleOnlineAnnounce(onlineannounce announce) + { + if (_loginCallback == null) + { + _logger.Log(LogType.Warning, "Received OnlineAnnounce but not expecting it."); + return; + } + + _logger.Log(LogType.Info, $"Login successful! UserID: {announce.Userid}, LocalSID: {announce.Localsid}"); + _currentUserId = announce.Userid; + _localsid = announce.Localsid; + + var callback = _loginCallback; + _loginCallback = null; + callback?.Invoke(true); + } + + private void RequestRoleListInternal(int lastHandle = -1) + { + rolelist rolelistRequest = new rolelist(); + rolelistRequest.Userid = _currentUserId; + rolelistRequest.Localsid = 0; + rolelistRequest.Handle = lastHandle; + + SendProtocol(rolelistRequest); + + + //gamedatasend gamedatasendRequest = new gamedatasend(); + //gamedatasendRequest.Data = C2SCommandFactory.CreatePlayerMove(); + + //SendProtocol(gamedatasendRequest); + } + + private void HandleRoleListResponse(RoleListResponse response) + { + if (_roleListCallback == null || _accumulatedRoles == null) + { + _logger.Log(LogType.Warning, "Received RoleListResponse but not expecting it."); + return; + } + + _logger.Log(LogType.Debug, + $"Received RoleListResponse. Handle: {response.handle}, Result: {response.result}, Count: {response.rolelist.Count}"); + + if (response.result == 0) + { + _accumulatedRoles.AddRange(response.rolelist); + + foreach (var role in response.rolelist) + { + try + { + string roleName = Encoding.UTF8.GetString(role.name.ByteArray, 0, role.name.Length); + _logger.Log(LogType.Info, $" - Role ID: {role.roleid}, Name: {roleName}, Level: {role.level}"); + } + catch (Exception ex) + { + _logger.Log(LogType.Error, $" - Error decoding role name: {ex.Message}"); + _logger.LogException(ex); + } + } + + if (response.handle != -1) + { + _logger.Log(LogType.Debug, $"Requesting next batch of roles (handle: {response.handle})..."); + RequestRoleListInternal(response.handle); + } + else + { + _logger.Log(LogType.Info, $"Finished fetching roles. Total count: {_accumulatedRoles.Count}"); + var callback = _roleListCallback; + var result = _accumulatedRoles; + _roleListCallback = null; + _accumulatedRoles = null; + PostToUnityContext(() => callback?.Invoke(result)); + } + } + else + { + _logger.Log(LogType.Error, $"Role list retrieval failed. Result code: {response.result}"); + FailRoleListInProgress($"Role list retrieval failed (Result: {response.result})"); + } + } + + // --- Helper methods for failure handling --- + private void FailLoginInProgress(string reason) + { + if (_loginCallback != null) + { + _logger.Log(LogType.Error, $"Login failed: {reason}"); + var callback = _loginCallback; + _loginCallback = null; + PostToUnityContext(() => callback?.Invoke(false)); + } + } + + private void FailRoleListInProgress(string reason) + { + if (_roleListCallback != null) + { + _logger.Log(LogType.Error, $"Role list retrieval failed: {reason}"); + var callback = _roleListCallback; + _roleListCallback = null; + _accumulatedRoles = null; + PostToUnityContext(() => callback?.Invoke(null)); + } + } + + // --- IDisposable Implementation --- + private bool disposedValue = false; + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + if (_networkManager != null) + { + _logger.Log(LogType.Info, "[DUCK] Disposing GameSession and disconnecting..."); + _networkManager.ProtocolReceived -= OnProtocolReceived; + _networkManager.ErrorOccurred -= OnErrorOccurred; + _networkManager.Disconnected -= OnDisconnected; + _networkManager.Disconnect(); + _networkManager.Dispose(); + _networkManager = null; + } + + _loginCallback = null; + _roleListCallback = null; + _accumulatedRoles = null; + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + // GC.SuppressFinalize(this); + } + + public bool ISPLAYERID(int id) + { + return id != 0 && (id & 0x80000000) == 0; + } + public bool ISNPCID(int id) => ((id & 0x80000000) != 0) && ((id & 0x40000000) == 0); + public bool ISMATTERID(int id) => ((id) & 0xC0000000) == 0xC0000000; + private byte[] GetBytes(byte[] bytes, int length, int index) + { + byte[] arrByteData = new byte[length]; + for (int i = 0; i < length; i++) + { + arrByteData[i] = bytes[i + index]; + } + + return arrByteData; + } + + public void c2s_CmdPlayerMove(in Vector3 vCurPos, in Vector3 vDest, + int iTime, float fSpeed, int iMoveMode, ushort wStamp) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = + C2SCommandFactory.CreatePlayerMove(vCurPos, vDest, (ushort)iTime, fSpeed, (byte)iMoveMode, wStamp); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdCastSkill(int idSkill, byte byPVPMask, int iNumTarget, int[] aTargets) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = + C2SCommandFactory.CreatePlayerCastSkill(idSkill, byPVPMask, iNumTarget, aTargets); + + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdCastInstantSkill(int idSkill, byte byPVPMask, int iNumTarget, int[] aTargets) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = + C2SCommandFactory.CreatePlayerCastInstantSkill(idSkill, byPVPMask, iNumTarget, aTargets); + + SendProtocol(gamedatasend); + } + + public void c2s_CmdCastPosSkill(int idSkill, Vector3 vDest, byte byPVPMask, int iNumTarget, int aTargets) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = + C2SCommandFactory.CreatePlayerCastPosSkill(idSkill, vDest, byPVPMask, iNumTarget, aTargets); + + SendProtocol(gamedatasend); + } + public void c2s_SendCmdContinueAction() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.CONTINUE_ACTION); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdStopMove(in Vector3 vDest, float fSpeed, int iMoveMode, + byte byDir, ushort wStamp, int iTime) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = + C2SCommandFactory.CreatePlayerStop(vDest, fSpeed, (byte)iMoveMode, byDir, wStamp, (ushort)iTime); + SendProtocol(gamedatasend); + } + + public void c2s_CmdSendEnterPKPrecinctint() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.ENTER_PK_PROTECTED); + SendProtocol(gamedatasend); + } + public void SendChatData(byte cChannel, in string szMsg, int iPack, int iSlot) + { + publicchat publicChat = new publicchat(); + publicChat.Channel = cChannel; + publicChat.Roleid = m_iCharID; + + byte[] unicodeBytes = Encoding.Unicode.GetBytes(szMsg); + publicChat.Msg.Replace(unicodeBytes); + _logger.Log(LogType.Warning, $"HoangDev : publicChat {publicChat}"); + SendProtocol(publicChat); + } + public void LoadConfigData() + { + getuiconfig p = new getuiconfig(); + p.Roleid = m_iCharID; + SendProtocol(p); + } + private void SetCharacterID(int iCharID) + { + m_iCharID = iCharID; + } + + private void OnPrtcChatMessage(Protocol pProtocol, bool bCalledagain) + { + chatmessage p = (chatmessage)pProtocol; + + string strTemp = System.Text.Encoding.Unicode.GetString(p.Msg.ToArray(), 0, p.Msg.Length); + + _logger.Log(LogType.Warning, $"HoangDev : OnPrtcChatMessage :{strTemp}"); + EventBus.Publish(new ChatMessageEvent(strTemp)); + } + + public struct ChatMessageEvent + { + public string context; + + public ChatMessageEvent(string context) + { + this.context = context; + } + } + public void OnPrtcGetConfigRe(Protocol pProtocol) + { + getuiconfig_re p = (getuiconfig_re)pProtocol; + if (p.Result != (int)ErrCode.ERR_SUCCESS) + BMLogger.LogError("CECGameSession::OnPrtcGetConfigRe, link return error code of " + p.Result); + else + { + if (!CECGameRun.Instance.LoadConfigsFromServer(p.UiConfig.RawBuffer, p.UiConfig.Size)) + { + // if load failed then use current setting directly + //TODO : fix later + EC_Game.GetConfigs().ApplyUserSetting(); + } + + // 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. + // Enalbe game UI + CECGameUIMan pGameUI = (CECGameUIMan)EC_Game.GetGameRun().GetUIManager().GetInGameUIMan(); + if (pGameUI != null) + { + pGameUI.EnableUI(true); + + // Get referral name for adding friend or other display + //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);*/ + } + + CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer(); + pHost.OnAllInitDataReady(); + + /* if (pHost.IsGM()) + { + CDlgCountryMap pDlgCountryMap = (CDlgCountryMap)pGameUI.GetDialog("Win_CountryMap"); + pDlgCountryMap.GetConfig(); + } + + g_pGame.GetConfigs().ApplyOptimizeSetting(); + + if (g_pGame.GetConfigs().IsMiniClient()) + CECMCDownload::GetInstance().SendGetDownloadOK();*/ + } + } + private void OnPrtcPlayerBaseInfoRe(Protocol pProtocol) + { + playerbaseinfo_re p = (playerbaseinfo_re)pProtocol; + BMLogger.Log($"OnPrtcPlayerBaseInfoRe: {p.Roleid} {p.Player.cls} {p.Player.gender}"); + EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERBASEINFO, MANAGER_INDEX.MAN_PLAYER, -1, p); + } + + public void c2s_CmdNPCSevAcceptTask(int idTask, int idStorage, int idRefreshItem) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateCmdNPCSevAcceptTask(idTask, idStorage, idRefreshItem); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdGetAllData(bool byPack, bool byEquip, bool byTask) + { + gamedatasend gamedatasend = new gamedatasend(); + + gamedatasend.Data = C2SCommandFactory.CreateGetAllDataCommand(byPack, byEquip, byTask); + _logger.Log(LogType.Warning, $"[Dat]- SendCmdGetAllData {byPack},{byEquip},{byTask}"); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevHello(int nid) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevHelloDataCommand(nid); + SendProtocol(gamedatasend); + } + + public void c2s_CmdNormalAttack(byte byPVPMask) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNormalAttackDataCmd(byPVPMask); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdCancelAction() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.CANCEL_ACTION); + SendProtocol(gamedatasend); + } + + public void c2s_CmdUnselect() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(CSNetwork.C2SCommand.CommandID.UNSELECT); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdSelectTarget(int idTarget) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateSelectTarget(idTarget); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevWaypoint() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevWaypointCmd(NPC_service_type.GP_NPCSEV_WAYPOINT, 0); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdNPCSevMakeItem(int idSkill, int idItem, uint dwCount) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevMakeItemCmd(idSkill, idItem, dwCount); + SendProtocol(gamedatasend); + } + public void GetRoleBaseInfo(int iNumRole, List aRoleIDs) + { + int iNumLimit = 128; + playerbaseinfo p = null; + int iCount = 0; + + while (iCount < iNumRole) + { + p = new(); + p.Roleid = _selectedRole.roleid; + + int iNumSend = iNumLimit; + if (iCount + iNumLimit > iNumRole) + iNumSend = iNumRole - iCount; + + if (iNumSend > 0) + { + p.playerList = new(); + for (int i = 0; i < iNumSend; i++) + p.playerList.Add(aRoleIDs[iCount + i]); + + SendProtocol(p); + } + + + iCount += iNumSend; + } + } + + public void c2s_SendCmdGetOtherEquip(int iNumID, List aIDs) + { + // int iNumLimit = 250; + // int iCount = 0; + + // while (iCount < iNumID) + // { + // int iNumSend = iNumLimit; + // if (iCount + iNumLimit > iNumID) + // iNumSend = iNumID - iCount; + + // if (iNumSend > 0) + // { + // } + // } + } + + public void c2s_SendCmdNPCSevAcceptTask(int idTask, int idStorage, int idRefreshItem) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateCmdNPCSevAcceptTask( + idTask, + idStorage, + idRefreshItem); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevReturnTask(int idTask, int iChoice) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevReturnTaskCmd( + idTask, + iChoice); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevTaskMatter(int idTask) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevTaskMatterCmd(idTask); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevLearnSkill(int idSkill) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevLearnSkillCmd(idSkill); + BMLogger.LogError("HoangDev : c2s_SendCmdNPCSevLearnSkill gamedatasend.Data : " + gamedatasend.Data.Size); + BMLogger.LogError("HoangDev : c2s_SendCmdNPCSevLearnSkill idSkill : " + idSkill); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevBuy(int itemNum, C2SCommand.npc_trade_item[] items) + { + if (itemNum <= 0 || items == null || items.Length < itemNum) + return; + + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevBuyCmd(itemNum, items); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevSell(int itemNum, C2SCommand.npc_sell_item[] items) + { + if (itemNum <= 0 || items == null || items.Length < itemNum) + return; + + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevSellCmd(itemNum, items); + SendProtocol(gamedatasend); + } + + public void GetRoleCustomizeData(int iNumRole, List aRoleIDs) + { + if (iNumRole <= 0 || aRoleIDs == null || aRoleIDs.Count == 0) return; + + int iNumLimit = 240; + int iCount = 0; + + while (iCount < iNumRole) + { + getcustomdata p = new(); + p.Roleid = _selectedRole.roleid; + + int iNumSend = iNumLimit; + if (iCount + iNumLimit > iNumRole) + iNumSend = iNumRole - iCount; + + for (int i = 0; i < iNumSend; i++) + p.playerlist.Add(aRoleIDs[iCount + i]); + + SendProtocol(p); + + iCount += iNumSend; + } + } + public void c2s_SendCmdEmoteAction(uint wPose) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateEmoteActionCmd((int)wPose); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdTaskNotify(byte[] pData, uint dwDataSize) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateTaskNotifyCmd( pData, dwDataSize); + BMLogger.Log($"[MH Task] c2s_SendCmdTaskNotify Command ID : {pData[0]} Size: {dwDataSize}"); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdStandUp() + { + gamedatasend gamedatasend = new gamedatasend(); + 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(); + gamedatasend.Data = C2SCommandFactory.CreateAutoTeamSetGoalCommand(type,goal_id, op); + SendProtocol(gamedatasend); + } + + public void c2s_CmdGoto(float x, float y, float z) + { + c2s_SendCmdGoto(x, y, z); + } + + // Send C2S::GOTO command data + void c2s_SendCmdGoto(float x, float y, float z) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateGoToCommed( x, y, z); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdUseItem(byte byPackage, byte bySlot, int tid, byte byCount) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateUseItemCmd(byPackage, bySlot, tid, byCount); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdGetExtProps() + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.GET_EXT_PROP); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdGivePresent(int roleid, int mail_id, int goods_id, int goods_index, int goods_slot) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateGivePresentCmd(roleid, mail_id, goods_id, goods_index, goods_slot); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdEnterSanctuary(int id) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateEnterSanctuaryCmd(id); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdEnterInstance(int iTransIdx, int idInst) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateEnterInstanceCmd(iTransIdx, idInst); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdActiveRushFly(bool bActive) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateActiveRushFlyCmd(bActive); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdPetCtrl(int idTarget, int cmd, byte[] pParamBuf, int iParamLen) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreatePetCtrlCmd(idTarget, cmd, pParamBuf, iParamLen); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdQueryFactionPVPInfo(int faction_id) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateQueryFactionPVPInfo(faction_id); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdNPCSevEmbed(ushort wStoneIdx, ushort wEquipIdx, int tidStone, int tidEquip) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevEmbedCmd(wStoneIdx, wEquipIdx, tidStone, tidEquip); + SendProtocol(gamedatasend); + } + public void c2s_SendCmdGetItemInfo(byte byPackage, int bySlot) + { + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateGetItemInfoCmd(byPackage, bySlot); + SendProtocol(gamedatasend); + } + } } \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/createrole.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/createrole.cs index 5bfe6615bf..9f7019acfb 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/createrole.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/createrole.cs @@ -1,23 +1,27 @@ using System; using System.Collections.Generic; +using CSNetwork.Protocols.RPCData; namespace CSNetwork.Protocols { public class createrole : Protocol { public int Userid { get; set; } - public int Localsid { get; set; } + public uint Localsid { get; set; } + public RoleInfo Roleinfo { get; set; } public Octets Referid { get; set; } public createrole() : base(ProtocolType.PROTOCOL_CREATEROLE) { Referid = new Octets(); + Roleinfo = new RoleInfo(); } public override Protocol Clone() => new createrole { Userid = Userid, Localsid = Localsid, + Roleinfo = Roleinfo?.Clone(), Referid = new Octets(Referid.ToArray()) }; @@ -25,13 +29,23 @@ namespace CSNetwork.Protocols { os.Write(Userid); os.Write(Localsid); + if (Roleinfo != null) + { + Roleinfo.Marshal(os); + } + else + { + new RoleInfo().Marshal(os); + } os.Write(Referid); } public override void Unmarshal(OctetsStream os) { Userid = os.ReadInt32(); - Localsid = os.ReadInt32(); + Localsid = os.ReadUInt32(); + Roleinfo = new RoleInfo(); + Roleinfo.Unmarshal(os); Referid = os.ReadOctets(); } diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/createrole_re.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/createrole_re.cs new file mode 100644 index 0000000000..fccf37d8b0 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/createrole_re.cs @@ -0,0 +1,58 @@ +using System; +using CSNetwork.Protocols.RPCData; + +namespace CSNetwork.Protocols +{ + public class createrole_re : Protocol + { + public int result { get; set; } + public int roleid { get; set; } + public uint localsid { get; set; } + public RoleInfo roleinfo { get; set; } + public int refretcode { get; set; } + + public createrole_re() : base(ProtocolType.PROTOCOL_CREATEROLE_RE) + { + roleinfo = new RoleInfo(); + } + + public override Protocol Clone() => new createrole_re + { + result = result, + roleid = roleid, + localsid = localsid, + roleinfo = roleinfo?.Clone(), + refretcode = refretcode + }; + + public override void Marshal(OctetsStream os) + { + os.Write(result); + os.Write(roleid); + os.Write(localsid); + if (roleinfo != null) + { + roleinfo.Marshal(os); + } + else + { + new RoleInfo().Marshal(os); + } + os.Write(refretcode); + } + + public override void Unmarshal(OctetsStream os) + { + result = os.ReadInt32(); + roleid = os.ReadInt32(); + localsid = os.ReadUInt32(); + roleinfo = new RoleInfo(); + roleinfo.Unmarshal(os); + refretcode = os.ReadInt32(); + } + + public override int PriorPolicy() => 101; + + public override bool SizePolicy(int size) => size <= 8192; + } +} diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/createrole_re.cs.meta b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/createrole_re.cs.meta new file mode 100644 index 0000000000..9dfcaf18de --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/createrole_re.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3640555d366b4e34bbefbbf040f7f339 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/rpcdata/GRoleInventory.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/rpcdata/GRoleInventory.cs index a9d2a545ef..5dd802d59e 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/rpcdata/GRoleInventory.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/rpcdata/GRoleInventory.cs @@ -35,7 +35,8 @@ namespace CSNetwork.Protocols.RPCData os.Write(pos); os.Write(count); os.Write(max_count); - os.Write(data); + // Server-side expects an Octets field here; null will crash encoding. + os.Write(data ?? new Octets()); os.Write(proctype); os.Write(expire_date); os.Write(guid1); diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/rpcdata/RoleInfo.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/rpcdata/RoleInfo.cs index 2929615f2f..ec05255c04 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/rpcdata/RoleInfo.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Protocols/rpcdata/RoleInfo.cs @@ -69,9 +69,10 @@ namespace CSNetwork.Protocols.RPCData os.Write(occupation); os.Write(level); os.Write(level2); - os.Write(name); - os.Write(custom_data); - os.WriteList(equipment); + // Avoid null Octets/List crashing protocol.Encode() + os.Write(name ?? new Octets()); + os.Write(custom_data ?? new Octets()); + os.WriteList(equipment ?? new List()); os.Write(status); os.Write(delete_time); os.Write(create_time); @@ -80,12 +81,12 @@ namespace CSNetwork.Protocols.RPCData os.Write(posy); os.Write(posz); os.Write(worldtag); - os.Write(custom_status); - os.Write(charactermode); + os.Write(custom_status ?? new Octets()); + os.Write(charactermode ?? new Octets()); os.Write(referrer_role); os.Write(cash_add); - os.Write(reincarnation_data); - os.Write(realm_data); + os.Write(reincarnation_data ?? new Octets()); + os.Write(realm_data ?? new Octets()); } public void Unmarshal(OctetsStream os) diff --git a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs index 4db362886e..a23f1aa2af 100644 --- a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs @@ -1,4 +1,4 @@ -using BrewMonster; +using BrewMonster; using BrewMonster.Common; using CSNetwork; using CSNetwork.C2SCommand; @@ -171,6 +171,12 @@ namespace BrewMonster.Network { Instance._gameSession.SelectRoleAsync(roleInfo, callback); } + + public static void CreateRoleAsync(RoleInfo roleInfo, Octets referId, Action callback = null) + { + Instance._gameSession.CreateRoleAsync(roleInfo, referId, callback); + } + public static void EnterWorldAsync(RoleInfo roleInfo, Action callback = null) { Debug.Log("EnterWorldAsync !!!!! nay "); diff --git a/Assets/PerfectWorld/Scripts/UI/Login/CreateCharacterScreen.cs b/Assets/PerfectWorld/Scripts/UI/Login/CreateCharacterScreen.cs new file mode 100644 index 0000000000..47b5ad4bf7 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/Login/CreateCharacterScreen.cs @@ -0,0 +1,249 @@ +using System; +using System.Text; +using BrewMonster.Network; +using CSNetwork; +using CSNetwork.GPDataType; +using CSNetwork.Protocols; +using CSNetwork.Protocols.RPCData; +using TMPro; +using UnityEngine; +using UnityEngine.UI; +using BrewMonster.Scripts; + +namespace BrewMonster.UI +{ + /// + /// UI screen for creating a new character. + /// Equivalent to CDlgCreateGenderName + CDlgCreateProfession in C++. + /// + public class CreateCharacterScreen : MonoBehaviour + { + [SerializeField] private GameObject professionSelectionPanel; + [SerializeField] private Button[] professionButtons; + [SerializeField] private Button maleGenderButton; + [SerializeField] private Button femaleGenderButton; + [SerializeField] private TMP_InputField nameInputField; + [SerializeField] private Button confirmButton; + [SerializeField] private Button cancelButton; + [SerializeField] private Button backButton; + + private int _currentProfession = -1; + private int _currentGender = -1; + + private Action _onCreateComplete; + private Action _onCancel; + + // Static array matching s_bShowMale[NUM_PROFESSION] from original C++ code (EC_ProfConfigs.cpp) + // true = show male, false = show female + private static readonly bool[] s_bShowMale = new bool[] + { + true, // 0: Warrior (武侠) + false, // 1: Mage (法师) + false, // 2: Priest (巫师) + false, // 3: Assassin (妖精) + true, // 4: Orc (妖兽) + true, // 5: Monk (刺客) + true, // 6: Elf (羽芒) + false, // 7: Elf (羽灵) + true, // 8: Ling (剑灵) + false, // 9: Ling (魅灵) + true, // 10: Oboro (夜影) + false, // 11: Oboro (月仙) + }; + + private void Start() + { + if (confirmButton != null) + confirmButton.onClick.AddListener(OnConfirmClicked); + if (cancelButton != null) + cancelButton.onClick.AddListener(OnCancelClicked); + if (backButton != null) + backButton.onClick.AddListener(OnCancelClicked); + + if (maleGenderButton != null) + maleGenderButton.onClick.AddListener(() => OnGenderSelected(GENDER.GENDER_MALE)); + if (femaleGenderButton != null) + femaleGenderButton.onClick.AddListener(() => OnGenderSelected(GENDER.GENDER_FEMALE)); + + // Setup profession buttons + if (professionButtons != null) + { + for (int i = 0; i < professionButtons.Length && i < 12; i++) + { + int prof = i; // Capture for closure + if (professionButtons[i] != null) + professionButtons[i].onClick.AddListener(() => OnProfessionSelected(prof)); + } + } + + if (nameInputField != null) + { + nameInputField.onSubmit.AddListener((text) => { if (CanConfirm()) OnConfirmClicked(); }); + } + } + + public void Show(Action onCreateComplete, Action onCancel) + { + _onCreateComplete = onCreateComplete; + _onCancel = onCancel; + _currentProfession = -1; + _currentGender = -1; + + gameObject.SetActive(true); + + if (nameInputField != null) + { + nameInputField.text = ""; + nameInputField.Select(); + } + + UpdateConfirmButtonState(); + } + + public void Hide() + { + gameObject.SetActive(false); + } + + private void OnProfessionSelected(int profession) + { + if (profession < 0 || profession >= (int)Profession.NUM_PROFESSION) + return; + + _currentProfession = profession; + + // Update UI to show selected profession + if (professionButtons != null) + { + for (int i = 0; i < professionButtons.Length; i++) + { + if (professionButtons[i] != null) + { + // Visual feedback for selected profession + var colors = professionButtons[i].colors; + colors.normalColor = (i == profession) ? Color.yellow : Color.white; + professionButtons[i].colors = colors; + } + } + } + + // Auto-select gender based on profession (matching original C++ logic from EC_ProfConfigs.cpp) + // This matches the s_bShowMale array in CanShowOnCreate function + int autoGender = GetDefaultGenderForProfession(profession); + OnGenderSelected(autoGender); + + UpdateConfirmButtonState(); + } + + /// + /// Gets the default gender for a profession based on the original C++ logic. + /// Matches the s_bShowMale array from EC_ProfConfigs.cpp CanShowOnCreate function. + /// + private int GetDefaultGenderForProfession(int profession) + { + if (profession >= 0 && profession < s_bShowMale.Length) + { + return s_bShowMale[profession] ? GENDER.GENDER_MALE : GENDER.GENDER_FEMALE; + } + + // Fallback to male if profession is invalid + return GENDER.GENDER_MALE; + } + + private void OnGenderSelected(int gender) + { + if (gender != GENDER.GENDER_MALE && gender != GENDER.GENDER_FEMALE) + return; + + _currentGender = gender; + + // Update UI to show selected gender + if (maleGenderButton != null) + { + var colors = maleGenderButton.colors; + colors.normalColor = (gender == GENDER.GENDER_MALE) ? Color.yellow : Color.white; + maleGenderButton.colors = colors; + } + + if (femaleGenderButton != null) + { + var colors = femaleGenderButton.colors; + colors.normalColor = (gender == GENDER.GENDER_FEMALE) ? Color.yellow : Color.white; + femaleGenderButton.colors = colors; + } + + UpdateConfirmButtonState(); + } + + private void OnConfirmClicked() + { + if (!CanConfirm()) + return; + + string characterName = nameInputField != null ? nameInputField.text : ""; + if (string.IsNullOrWhiteSpace(characterName)) + { + Debug.LogWarning("Character name cannot be empty"); + return; + } + + // Create RoleInfo using helper method + RoleInfo roleInfo = GameSession.CreateNewRoleInfo(characterName, _currentProfession, _currentGender); + + // Create role via network + Debug.Log($"Calling CreateRoleAsync for character: {characterName}, profession: {_currentProfession}, gender: {_currentGender}"); + UnityGameSession.CreateRoleAsync(roleInfo, new Octets(), (createdRole) => + { + if (createdRole != null) + { + Debug.Log($"Character created successfully: {characterName}, RoleID: {createdRole.roleid}"); + Hide(); + _onCreateComplete?.Invoke(createdRole); + } + else + { + Debug.LogError($"Failed to create character: {characterName}. Check GameSession logs for error details."); + // TODO: Show error message to user + } + }); + } + + private void OnCancelClicked() + { + Hide(); + _onCancel?.Invoke(); + } + + private bool CanConfirm() + { + if (_currentProfession < 0 || _currentProfession >= (int)Profession.NUM_PROFESSION) + return false; + + if (_currentGender != GENDER.GENDER_MALE && _currentGender != GENDER.GENDER_FEMALE) + return false; + + string name = nameInputField != null ? nameInputField.text : ""; + if (string.IsNullOrWhiteSpace(name)) + return false; + + return true; + } + + private void UpdateConfirmButtonState() + { + if (confirmButton != null) + { + confirmButton.interactable = CanConfirm(); + } + } + + private void Update() + { + // Update confirm button state in case name changes + if (nameInputField != null && nameInputField.isFocused) + { + UpdateConfirmButtonState(); + } + } + } +} diff --git a/Assets/PerfectWorld/Scripts/UI/Login/CreateCharacterScreen.cs.meta b/Assets/PerfectWorld/Scripts/UI/Login/CreateCharacterScreen.cs.meta new file mode 100644 index 0000000000..5a22e1e624 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/Login/CreateCharacterScreen.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: aa9de023137e92348983cee3c59d620b \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs index 0664d235eb..559b51cac9 100644 --- a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs +++ b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs @@ -1,193 +1,282 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using BrewMonster.Network; -using CSNetwork.Protocols; -using CSNetwork.Protocols.RPCData; -using TMPro; -using UnityEngine; -using UnityEngine.SceneManagement; -using UnityEngine.UI; - -namespace BrewMonster.UI -{ - /// - /// Login Flow: - /// 1. Enter username and password - /// 2. Click login button - /// 3. Login success, get the list of characters - /// 4. Open the select character screen - /// - public class LoginScreenUI : MonoBehaviour - { - [SerializeField] private TMP_InputField _usernameInputField; - [SerializeField] private TMP_InputField _passwordInputField; - [SerializeField] private Button _loginButton; - [SerializeField] private SelecScreenCharacter _selectCharacterScreen; - - private List _roleInfos; - bool isDoneWorldRender = false; - bool isDoneNPCRender = false; - private SynchronizationContext context; - public AudioClip loginBGM; - void Start() - { - AudioManager.Instance.PlayBGM(loginBGM, 1.5f); - _loginButton.onClick.AddListener(OnLoginButtonClicked); - context = SynchronizationContext.Current; - - _usernameInputField.text = PlayerPrefs.GetString("username", ""); - _passwordInputField.text = PlayerPrefs.GetString("password", ""); - - } - - // Update is called once per frame - void Update() - { - if (_roleInfos != null) - { - _selectCharacterScreen.InitScreen(_roleInfos, OnClickSelectCharacter); - _roleInfos = null; - } - -#if UNITY_EDITOR - if (Input.GetKeyUp(KeyCode.LeftAlt)) - { - _usernameInputField.text = "test004"; - _passwordInputField.text = "123456"; - } - - if (Input.GetKeyUp(KeyCode.Tab)) - { - _usernameInputField.text = "test002"; - _passwordInputField.text = "123456"; - OnLoginButtonClicked(); - } -#endif - } - - public async void OnLoginButtonClicked() - { - BMLogger.Log("OnLoginButtonClicked"); - string username = _usernameInputField.text; - string password = _passwordInputField.text; - // UnityGameSession.SetConnectionInfo("103.182.22.52", 29000); - UnityGameSession.SetConnectionInfo("103.51.120.195", 29000); - PlayerPrefs.SetString("username", username); - PlayerPrefs.SetString("password", password); - PlayerPrefs.Save(); - await UnityGameSession.Login(username, password, OnLoginComplete); - _selectCharacterScreen.gameObject.SetActive(true); - } - - /// - /// Callback when the login is complete. - /// Then get the list of characters - /// - private void OnLoginComplete(bool result) - { - if (!result) - { - BMLogger.LogError("Login failed"); - return; - } - - UnityGameSession.GetRoleListAsync(OnGetRoleListComplete); - } - - /// - /// Callback when the list of characters is retrieved. - /// Then move to the select character screen - /// - private void OnGetRoleListComplete(List roleInfos) - { - _roleInfos = roleInfos; - } - - private void OnClickSelectCharacter(RoleInfo roleInfo) - { - UnityGameSession.SelectRoleAsync(roleInfo, OnSelectRoleComplete); - } - - private void OnSelectRoleComplete(RoleInfo roleInfo) - { - context.Post(_ => - { - isDoneWorldRender = false; - isDoneNPCRender = false; - Action actLoadChar = () => - { - if (!isDoneNPCRender || !isDoneWorldRender) - { - return; - } - }; - SceneLoader.SceneLoadProcess = SceneLoadProcess.Loading; - SceneLoader.LoadingProgress = 0; - LoadingSceneController.Instance.ShowLoadingScene(true); -#if TESTFAST - string nameScene = "LoginScene"; - SceneManager.UnloadSceneAsync(nameScene); - isDoneNPCRender = true; - isDoneWorldRender = true; - actLoadChar?.Invoke(); - UnityGameSession.EnterWorldAsync(roleInfo, OnEnterWorldComplete); -#else - string nameScene = "NPCRender"; - UnityGameSession.Instance.LoadScene(nameScene, LoadSceneMode.Single, (value) => - { - isDoneNPCRender = value; - actLoadChar?.Invoke(); - }); - nameScene = "a61"; - UnityGameSession.Instance.LoadScene(nameScene, LoadSceneMode.Additive, (value) => - { - isDoneWorldRender = value; - actLoadChar?.Invoke(); - UnityGameSession.EnterWorldAsync(roleInfo, OnEnterWorldComplete); - - }); -#endif - }, null); - } - - private async void OnEnterWorldComplete() - { - await Task.Delay(2000); - // Request all known packages: 0=Inventory,1=Equipment,2=Task - UnityGameSession.RequestAllInventoriesAsync(() => { /*BMLogger.Log("Sent Inventory Detail Requests (all packs)");*/ }, 0, 1, 2); - await Task.Delay(1000); - UnityGameSession.RequestCheckSecurityPassWd(""); - await Task.Delay(1000); - } - - //private void OnInventoryReceived(List inventoryData) - //{ - // _inventoryUI.DisplayInventory(inventoryData); - //} - -#if UNITY_EDITOR - private void OnValidate() - { - if (_usernameInputField == null) - { - // find childrend with name "username" - _usernameInputField = transform.Find("username").GetComponent(); - } - - if (_passwordInputField == null) - { - // find childrend with name "password" - _passwordInputField = transform.Find("password").GetComponent(); - } - - if (_loginButton == null) - { - // find childrend with name "LoginBtn" - _loginButton = transform.Find("LoginBtn").GetComponent