using BrewMonster.Assets.PerfectWorld.Scripts.Players; using BrewMonster.Managers; using BrewMonster.Network; using BrewMonster.PerfectWorld.Scripts.Vfx; using BrewMonster.Scripts; using BrewMonster.Scripts.Managers; using BrewMonster.Scripts.Pet; using BrewMonster.Scripts.Skills; using BrewMonster.Scripts.World; using BrewMonster.UI; using CSNetwork; using CSNetwork.GPDataType; using CSNetwork.Protocols.RPCData; using Cysharp.Threading.Tasks; using ModelRenderer.Scripts.GameData; using PerfectWorld.Scripts; using PerfectWorld.Scripts.Managers; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using UnityEngine; using UnityEngine.UI; using static BrewMonster.CECPlayer; using cmd_select_target = CSNetwork.GPDataType.cmd_select_target; using cmd_player_chgshape = CSNetwork.GPDataType.cmd_player_chgshape; using Host_work_ID = BrewMonster.Scripts.CECHPWork.Host_work_ID; using ObjectCoords = System.Collections.Generic.List; using Trace_reason = BrewMonster.CECHPWorkTrace.Trace_reason; using System.Threading.Tasks; using static BrewMonster.EC_CDR; namespace BrewMonster { public partial class CECHostPlayer : CECPlayer { [SerializeField] private CharacterController controller; [SerializeField] private Joystick joystick; [SerializeField] private Button btnJump; [SerializeField] private Button btnRun; public CECHostMove m_MoveCtrl; private CECHPWorkMan m_pWorkMan; // Host work manager private uint m_dwLIES; // Logic-influence extend states private FACTION_FORTRESS_ENTER m_fortressEnter; // ½øÈë»ùµØÐÅÏ¢ //private PVPINFO m_pvp; // pvp information //private bool m_bInSanctuary = false; // true, player is in sanctuary //private int m_idFaction = 0; // ID of player's faction public bool m_bPrepareFight = false; // true, prepare to fight private int m_iJumpCount = 0; private bool m_bJumpInWater = false; public A3DVECTOR3 m_vVelocity; // Velocity bool m_bChangingFace; // true, host is changing face private int m_iRoleCreateTime; private int m_iRoleLastLoginTime; // Role last login time private int m_iAccountTotalCash; private CECPetCorral m_pPetCorral; private List m_aTabSels = new List(); private int m_lastAutoSelectNearestId = 0; private List m_aPtSkills = new List(); private List m_aPsSkills = new List(); private List m_aEquipSkills = new List(); private List m_aGoblinSkills = new List(); #if UNITY_EDITOR [SerializeField] private int m_startingSkillID = 0; // Starting skill ID for H key shortcut (0 = use cycling, >0 = use specific ID) private int m_currentSkillCycleIndex = 0; #endif private bool m_bEnterGame; public CECSkill m_pPrepSkill; private float playerSpeed = 5.0f; private float jumpHeight = 1.5f; private float gravityValue = -9.81f; private StateAnim stateAnim = StateAnim.Idle; private Vector3 playerVelocity; private bool isGrounded = false; private bool isRun = false; private Vector3 m_vLastSevPos; public CDR_INFO m_CDRInfo; public GNDINFO m_GndInfo; private int m_idUCSelTarget; // Uncertificately selected object's ID public float m_fVertSpeed = 0f; int m_idSevNPC = 0; // Current service NPC bool m_bTalkWithNPC = false; // true, is talking with NPC List m_aWayPoints = new List(); // Active way points bool m_bIsInKingService = false; // ÊÇ·ñÕýÔÚ½øÐйúÍõ·þÎñ²Ù×÷ CECActionSwitcherBase m_pActionSwitcher; private int m_iWorldContribution; private int m_iWorldContributionSpend; private bool m_bSpellDSkill; private CECSkill m_pTargetItemSkill; // Target item skill public CECComboSkill m_pComboSkill; // Combo skill public A3DVECTOR3 m_vAccel; // Accelerate\ public byte m_RealmLevel; REINCARNATION_TOME m_ReincarnationTome; // תÉú /// Used by for fight-channel EXP strings (book vs normal). internal bool IsReincarnationTomeActive => m_ReincarnationTome.tome_active != 0; public bool m_bRushFly = false; // true, in rush fly mode private CECCounter m_IncantCnt; private bool m_bMelee; private int MAX_JUMP_COUNT = 2; bool m_bUsingTrashBox = false; // Whether being using trash box private float m_fPrayDistancePlus; private bool m_bInRebuildPet = false; // Whether rebuilding pet / 是否正在重建宠物 private uint m_dwGMFlags = 0; // GM flags / GM标志 private A3DVECTOR3 g_vOrigin = new A3DVECTOR3(0f); private float EC_SLOPE_Y = 0.5f; int m_iOldWalkMode = Move_Mode.MOVE_STAND; // Copy of work mode public uint m_dwMoveRelDir = 0; // Move relative direction flags public ON_AIR_CDR_INFO m_AirCDRInfo; public ushort m_wMoveStamp = 0; private CECCounter m_GatherCnt; // Gather counter Dictionary m_skillCoolTime = new Dictionary(); COOLTIME[] m_aCoolTimes = new COOLTIME[(int)CoolTimeIndex.GP_CT_MAX]; // Cool times CECCounter m_PetOptCnt = new CECCounter(); // Pet operation time counter protected bool[] m_playerLimits = new bool[(int)PLAYER_LIMIT.PLAYER_LIMIT_MAX]; RaycastHitDistanceComparer raycastHitDistanceComparer = new RaycastHitDistanceComparer(); // תÉú´ÎÊý byte m_ReincarnationCount = 0; // Host config data version const int HOSTCFG_VERSION = 11; // Favorite auction version const int FAVOR_AUCTION_VERSION = 1; // ID of return-town skill const int ID_RETURNTOWN_SKILL = 167; // ID of summon player skill const int ID_SUMMONPLAYER_SKILL = 1824; private CECAutoTeam m_pAutoTeam; private CECTeam m_pTeam; // Current team (null when not in team) // ====== Ground cast config ====== [Header("Ground Cast")] [Tooltip("Khoảng thêm ngoài skinWidth để SphereCast xuống (m ngắn)")] [SerializeField] private float extraGroundDistance = 1f; [Tooltip("Bớt bán kính một chút để tránh tự va vào capsule (epsilon)")] [SerializeField] private float radiusEpsilon = 0.005f; [Tooltip("Layer mặt đất")] [SerializeField] private LayerMask groundMask = 1 << 6; [Tooltip("Layer mặt đất")] [SerializeField] private float slopeToleranceDeg = 2f; // cache tùy chọn (không bắt buộc) float ccRadius, ccSkin; //RaycastHit lastGroundHit; Camera mainCam; Ray ray; RaycastHit[] hits = new RaycastHit[5]; bool isDataAwaitToReady = false; public bool isTerrainToReady = false; public bool isLitToReady = false; private BaseVfxObject m_pSelectedGFX; private BaseVfxObject m_pHoverGFX; // ── Movement sound (C++ equivalent: m_pCurMoveSnd) ────────────────── private int _curMoveSndId = -1; // currently playing move-sound ID (-1 = none) private int _moveSndUpdateCounter; // 3-tick throttle counter // Cursor estimation optimization private Vector2 m_lastMousePosition; private float m_cursorUpdateTimer; private const float CURSOR_UPDATE_INTERVAL = 0.05f; // 20 times per second instead of 60+ private UnityEngine.InputSystem.Mouse m_cachedMouse; private UnityEngine.InputSystem.Keyboard m_cachedKeyboard; int[] targetsCastSkill; public bool IsChangingFace() { return m_bChangingFace; } // ===== Inventory packs (instance-based) ===== // 0 = normal pack, 1 = equip pack, 2 = task pack (see InventoryConst.IVTRTYPE_*) private readonly EC_Inventory m_pPack = new EC_Inventory(); /// /// m_pEquipPack is changed to m_equipInventory /// private readonly EC_Inventory m_pEquipPack = new EC_Inventory(); private readonly EC_Inventory m_pTaskPack = new EC_Inventory(); public EC_Inventory IvtrPack => m_pPack; public EC_Inventory IvtrEquipPack => m_pEquipPack; public EC_Inventory IvtrTaskPack => m_pTaskPack; public Transform PointCam { get => pointCam; } private void OnApplicationQuit() { if (m_pTaskInterface != null) m_pTaskInterface.Despose(); } public bool IsMeleeing() { return m_bMelee; } public CECAutoTeam GetAutoTeam() { return m_pAutoTeam; } public override CECTeam GetTeam() { return m_pTeam; } internal void SetTeam(CECTeam team) { m_pTeam = team; } public EC_Inventory GetPack() { return m_pPack; } public EC_Inventory GetTaskPack() { return m_pTaskPack; } public EC_Inventory GetEquipment() { return m_pEquipPack; } // Get work manager // 获取工作管理器 public CECHPWorkMan GetWorkMan() { return m_pWorkMan; } // Get navigate player // 获取导航玩家 public int GetWorldContribution() { return m_iWorldContribution; } public int GetWorldContributionSpend() { return m_iWorldContributionSpend; } private CECHostNavigatePlayer m_pNavigatePlayer = null; public CECHostNavigatePlayer GetNavigatePlayer() { return m_pNavigatePlayer; } // Check if in force navigate state // 检查是否在强制导航状态 public bool IsInForceNavigateState() { CECHostNavigatePlayer pNavigatePlayer = GetNavigatePlayer(); if (pNavigatePlayer != null && pNavigatePlayer.GetNavigateCtrl() != null) { return pNavigatePlayer.GetNavigateCtrl().IsInForceNavigateState(); } return false; } // Handle navigation event // 处理导航事件 public void OnNaviageEvent(int task, int e) { UnityEngine.Debug.Log($"[CECHostPlayer] OnNaviageEvent: Task={task}, Event={e} ({(BrewMonster.Scripts.CECNavigateCtrl.NavigateEvent)e})"); if (e == (int)CECNavigateCtrl.NavigateEvent.EM_PREPARE) { CreateNavigatePlayer(); } if (m_pNavigatePlayer != null) { m_pNavigatePlayer.OnNavigateEvent(task, e); } if(e == (int)CECNavigateCtrl.NavigateEvent.EM_END) { ReleaseNavigatePlayer(); } } private void CreateNavigatePlayer() { ReleaseNavigatePlayer(); GameObject navigateGo = new GameObject("HostNavigatePlayer"); navigateGo.transform.SetParent(transform, false); navigateGo.transform.localScale = Vector3.one; navigateGo.AddComponent(); CECHostNavigatePlayer navigatePlayer = navigateGo.AddComponent(); navigatePlayer.InitializeHost(this); //navigatePlayer.LoadConfig(); m_pNavigatePlayer = navigatePlayer; } private void ReleaseNavigatePlayer() { if (m_pNavigatePlayer != null) { m_pNavigatePlayer.Release(); m_pNavigatePlayer = null; } } public EC_Inventory GetInventory(byte byPackage) { switch (byPackage) { case InventoryConst.IVTRTYPE_PACK: return m_pPack; case InventoryConst.IVTRTYPE_EQUIPPACK: return m_pEquipPack; case InventoryConst.IVTRTYPE_TASKPACK: return m_pTaskPack; default: return null; } } private void Awake() { base.Awake(); m_MoveCtrl = new CECHostMove(this); // Cache: không bắt buộc, nhưng gọn tay và ít gọi property lặp. if (controller != null) { ccRadius = controller.radius; ccSkin = controller.skinWidth; } // 任务状态检查节流(毫秒) // Throttle task status check (milliseconds) // NOTE: CECCounter uses "Period" as threshold; SetCounter only sets current value. // We want task checking roughly every 3 seconds, not every frame. m_TaskCounter.SetPeriod(3000f); m_TaskCounter.Reset(true); // trigger first check immediately m_bTitleDataReady = false; //playerTransform = transform; m_GatherCnt ??= new CECCounter(); m_GatherCnt.SetPeriod(1000); m_GatherCnt.Reset(true); m_PetOptCnt.SetPeriod(1000); m_PetOptCnt.Reset(true); CreateInventories(); // run a process on background to keep track of task status. TickTask().Forget(); } public async Task LoadResources() { //BMLogger.LogError("HoangDev: CECHostPlayer::LoadResources"); RoleInfo RoleInfo = UnityGameSession.Instance.GetRoleInfo(); m_iProfession = RoleInfo.occupation; m_iGender = RoleInfo.gender; m_iRoleCreateTime = RoleInfo.create_time; m_iRoleLastLoginTime = RoleInfo.lastlogin_time; m_iAccountTotalCash = RoleInfo.cash_add; EC_Game.GetGameRun().AddPlayerName(m_PlayerInfo.cid, m_strName); if (!await LoadPlayerSkeleton(true)) { BMLogger.LogError($"HoangDev CECHostPlayer::LoadResources, Failed to load skeleton. {m_strName}"); return false; } return true; } private void Start() { mainCam = Camera.main; if (mainCam == null) { mainCam = FindFirstObjectByType(); } // btnJump.onClick.AddListener(HandleJump); // Cache input devices for better performance m_cachedMouse = UnityEngine.InputSystem.Mouse.current; m_cachedKeyboard = UnityEngine.InputSystem.Keyboard.current; } protected override void Update() { base.Update(); if (!isDataAwaitToReady || !isTerrainToReady || !isLitToReady) { return; } // Always set the pointCam's world rotation value to Vector3.zero // because rotating the character with the camera will cause a lagging effect. if (pointCam != null) { pointCam.rotation = Quaternion.identity; } // Update cursor based on what's under mouse EstimateCursor(); //Debug.Log($"(ulong)Time.deltaTime * 1000 {(ulong)(Time.deltaTime * 1000)}"); m_MoveCtrl.Tick((ulong)(Time.deltaTime * 1000)); // Nếu có thay đổi runtime, có thể lấy lại mỗi vài giây/Start nếu bạn thích: // ccRadius = controller.radius; ccSkin = controller.skinWidth; EstimateMoveEnv(GetPos()); OnKeyDown(); // Update timers UpdateTimers(Time.deltaTime); m_pWorkMan?.Tick(Time.deltaTime); // Update GFXs UpdateGFXs(Time.deltaTime); // Update movement sound UpdateMoveSound(); //m_dwMoveRelDir = 0; m_fVertSpeed = 0.0f; // Auto team / Automatic party grouping // m_pAutoTeam.Tick(Time.deltaTime); // TODO: Remove later // UpdatePosWing(); } private void JoystickRelease(JoystickRealeaseEvent joystickRealeaseEvent) { // _playerStateMachine.ChangeState(_idleState); m_dwMoveRelDir = 0; } public bool GroundCheck(out RaycastHit hit) { float radius = controller.radius; float skin = controller.skinWidth; float height = controller.height; // Tâm capsule theo world Vector3 cWorld = transform.TransformPoint(controller.center); float hemi = Mathf.Max(0f, (height * 0.5f) - radius); // Hai điểm top/bottom của capsule nhân vật (đang đứng) Vector3 pTop = cWorld + Vector3.up * hemi; Vector3 pBottom = cWorld - Vector3.up * hemi; // Ta tạo một "đoạn capsule ngắn" gần đáy để sweep xuống // Nhấc đoạn bắt đầu lên 1 chút để không bắt đầu trong trạng thái giao nhau Vector3 startBottom = pBottom + Vector3.up * (skin + 0.01f); Vector3 startTop = startBottom + Vector3.up * (radius * 2f - 0.02f); // chiều cao đoạn ngắn ~2*radius float castRadius = Mathf.Max(0f, radius - radiusEpsilon); float castDistance = skin + extraGroundDistance; // quãng sweep ngắn bool hitSomething = Physics.CapsuleCast( startTop, startBottom, castRadius, Vector3.down, out hit, castDistance, groundMask, QueryTriggerInteraction.Ignore ); if (!hitSomething) return false; // Lọc theo slope limit float maxSlope = controller.slopeLimit + slopeToleranceDeg; float slope = Vector3.Angle(hit.normal, Vector3.up); if (slope > maxSlope) return false; return true; } private void HandleJump() { if (isGrounded) { playerVelocity.y = Mathf.Sqrt(jumpHeight * -2f * gravityValue); } } public void ProcessMessage(in ECMSG Msg) { var msg = (int)Msg.dwMsg; //Debug.LogError("HoangDev : ProcessMessageProcessMessageProcessMessage " + msg); switch (msg) { case EC_MsgDef.MSG_HST_CORRECTPOS: OnMsgHstCorrectPos(Msg); break; case EC_MsgDef.MSG_HST_GOTO: OnMsgHstGoto(Msg); break; case EC_MsgDef.MSG_HST_IVTRINFO: { OnMsgHstIvtrInfo(Msg); break; } case EC_MsgDef.MSG_HST_OWNITEMINFO: { OnMsgHstOwnItemInfo(Msg); break; } case EC_MsgDef.MSG_HST_TASKDATA: { OnMsgHstTaskData(Msg).Forget(); //Debug.LogError("[Dat]- OnMsgHstTaskData"); break; } case EC_MsgDef.MSG_HST_ITEMOPERATION: OnMsgHstItemOperation(Msg); break; case EC_MsgDef.MSG_HST_PICKUPITEM: OnMsgHstPickupItem(Msg); break; case EC_MsgDef.MSG_HST_PURCHASEITEMS: OnMsgHstPurchaseItems(Msg); break; case EC_MsgDef.MSG_HST_PRODUCEITEM: OnMsgHstProduceItem(Msg); break; case EC_MsgDef.MSG_HST_SELTARGET: OnMsgHstSelTarget(Msg); break; case EC_MsgDef.MSG_HST_USEITEM: OnMsgHstUseItem(Msg); break; case EC_MsgDef.MSG_HST_PICKUPMONEY: OnMsgHstPickupMoney(Msg); break; case EC_MsgDef.MSG_HST_SPENDMONEY: OnMsgHstSpendMoney(Msg); break; case EC_MsgDef.MSG_HST_ITEMTOMONEY: OnMsgHstItemToMoney(Msg); break; case EC_MsgDef.MSG_HST_ATKRESULT: OnMsgHstAttackResult(Msg); break; case EC_MsgDef.MSG_HST_ATTACKONCE: OnMsgHstAttackOnce(Msg); break; case EC_MsgDef.MSG_HST_ATTACKED: OnMsgHstAttacked(Msg); break; case EC_MsgDef.MSG_HST_HURTRESULT: OnMsgHstHurtResult(Msg); break; case EC_MsgDef.MSG_HST_INFO00: OnMsgHstInfo00(Msg); break; case EC_MsgDef.MSG_HST_NPCGREETING: OnMsgHstNPCGreeting(Msg); break; case EC_MsgDef.MSG_HST_WAYPOINT: OnMsgHstWayPoint(Msg); break; case EC_MsgDef.MSG_HST_SKILLDATA: OnMsgHstSkillData(Msg); break; case EC_MsgDef.MSG_HST_DIED: OnMsgHstDied(Msg); break; case EC_MsgDef.MSG_HST_STARTATTACK: OnMsgHstStartAttack(Msg); break; case EC_MsgDef.MSG_HST_STOPATTACK: OnMsgHstStopAttack(Msg); break; case EC_MsgDef.MSG_HST_SKILLRESULT: OnMsgHstSkillResult(Msg); break; case EC_MsgDef.MSG_HST_SKILLATTACKED: OnMsgHstSkillAttacked(Msg); break; case EC_MsgDef.MSG_PM_CASTSKILL: OnMsgPlayerCastSkill(Msg); break; case EC_MsgDef.MSG_HST_SETCOOLTIME: OnMsgHstSetCoolTime(Msg); break; case EC_MsgDef.MSG_PM_PLAYEREXTSTATE: OnMsgPlayerExtState(Msg); break; case EC_MsgDef.MSG_PM_ENCHANTRESULT: OnMsgEnchantResult(Msg); break; case EC_MsgDef.MSG_HST_LEARNSKILL: OnMsgHstLearnSkill(Msg); break; case EC_MsgDef.MSG_HST_COMBO_SKILL_PREPARE: OnMsgComboSkillPrepare(Msg); break; case EC_MsgDef.MSG_HST_CONTINUECOMBOSKILL: OnMsgContinueComboSkill(Msg); break; case EC_MsgDef.MSG_HST_OWNEXTPROP: OnMsgHstExtProp(Msg); break; case EC_MsgDef.MSG_PM_PLAYERDOEMOTE: OnMsgPlayerDoEmote(Msg); break; case EC_MsgDef.MSG_HST_TARGETISFAR: OnMsgHstTargetIsFar(Msg); break; case EC_MsgDef.MSG_PM_PLAYERGATHER: OnMsgPlayerGather(Msg); break; case EC_MsgDef.MSG_HST_COOLTIMEDATA: OnMsgHstCoolTimeData(Msg); break; case EC_MsgDef.MSG_HST_PRESSCANCEL: OnMsgHstPressCancel(Msg); break; case EC_MsgDef.MSG_PM_PLAYERFLY: OnMsgPlayerFly(Msg); break; case EC_MsgDef.MSG_HST_PETOPT: OnMsgHstPetOpt(Msg); break; case EC_MsgDef.MSG_HST_SETPLAYERLIMIT: OnMsgHstSetPlayerLimit(Msg); break; case EC_MsgDef.MSG_PM_PLAYERMOUNT: OnMsgPlayerMount(Msg); break; case EC_MsgDef.MSG_HST_EMBEDITEM: OnMsgHstEmbedItem(Msg); break; case EC_MsgDef.MSG_HST_CLEARTESSERA: OnMsgHstClearTessera(Msg); break; case EC_MsgDef.MSG_HST_JOINTEAM: OnMsgHstJoinTeam(Msg); break; case EC_MsgDef.MSG_HST_LEAVETEAM: OnMsgHstLeaveTeam(Msg); break; case EC_MsgDef.MSG_HST_NEWTEAMMEM: OnMsgHstNewTeamMem(Msg); break; case EC_MsgDef.MSG_HST_TEAMINVITE: OnMsgHstTeamInvite(Msg); break; case EC_MsgDef.MSG_HST_TEAMMEMBERDATA: OnMsgHstTeamMemberData(Msg); break; case EC_MsgDef.MSG_PM_DUELOPT: OnMsgHstDuelOpt(Msg); break; case EC_MsgDef.MSG_PM_PLAYERCHGSHAPE : OnMsgPlayerChgShape(Msg); break; case EC_MsgDef.MSG_HST_SETMOVESTAMP: OnMsgHstSetMoveStamp(Msg); break; case EC_MsgDef.MSG_HST_SANCTUARY: OnMsgHstSanctuary(Msg); break; case EC_MsgDef.MSG_HST_RECEIVEEXP: OnMsgHstReceiveExp(Msg); break; case EC_MsgDef.MSG_HST_ROOTNOTIFY: OnMsgHstRootNotify(Msg) ; break; default: // Uncomment to debug unhandled messages // Debug.LogWarning($"[CECHostPlayer] ProcessMessage: Unhandled message {msg}"); break; } /*if (bActionStartSkill) AP_ActionEvent(AP_EVENT_STARTSKILL, iActionTime); if (bDoOtherThing) { if (m_pComboSkill != null && !m_pComboSkill.IsStop()) { if (CECAutoPolicy.GetInstance().IsAutoPolicyEnabled()) g_pGame.GetGameRun().PostMessage(MSG_HST_CONTINUECOMBOSKILL, MAN_PLAYER, 0, 0, m_pComboSkill.GetGroupIndex()); else m_pComboSkill.Continue(false); } else { if (idTarget != 0 && idTarget != m_PlayerInfo.cid) NormalAttackObject(idTarget, true); } }*/ } private void OnMsgHstRootNotify(in ECMSG Msg) { if (Convert.ToInt32(Msg.dwParam2) == CommandID.HOST_NOTIFY_ROOT) { cmd_host_notify_root pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); m_dwLIES |=(uint) (1 << pCmd.type); if (pCmd.type != 3) { // Force pull host to specified position SetPos(EC_Utility.ToVector3(pCmd.pos)); } if (IsRooting()) { if (m_pWorkMan.IsFollowing()){ m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_FOLLOW); } if (m_pWorkMan.IsMovingToPosition()){ m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_MOVETOPOS); } if (m_pWorkMan.IsTracing()){ m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_TRACEOBJECT); } } } else if (Convert.ToInt32(Msg.dwParam2) == CommandID.HOST_DISPEL_ROOT) { cmd_host_dispel_root pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); m_dwLIES &=(uint) ~(1 << pCmd.type); } } private void OnMsgHstPickupMoney(ECMSG msg) { var data = msg.dwParam1 as byte[]; if (data == null || data.Length == 0) return; cmd_pickup_money pCmd = GPDataTypeHelper.FromBytes(data); AddMoneyAmount(pCmd.amount); CECGameRun pGameRun = EC_Game.GetGameRun(); pGameRun.AddFixedMessage((int)FixedMsg.FIXMSG_PICKUPMONEY, pCmd.amount); BubbleText((int)BubbleTextType.BUBBLE_MONEY, (uint)pCmd.amount); } private void OnMsgHstSpendMoney(ECMSG msg) { var data = msg.dwParam1 as byte[]; if (data == null || data.Length == 0) return; cmd_spend_money pCmd = GPDataTypeHelper.FromBytes(data); AddMoneyAmount(-(int)pCmd.cost); } /// /// Sell to NPC: server sends ITEM_TO_MONEY. Removes sold items from pack and adds gained money. /// Payload layout differs across server builds, so we parse defensively. /// private void OnMsgHstItemToMoney(ECMSG Msg) { var data = Msg.dwParam1 as byte[]; if (data == null || data.Length < 4) return; // Two observed/expected formats: // A) [u32 itemCount] + itemCount * npc_sell_item(16 bytes) // B) [u32 moneyGain] [u32 itemCount] + itemCount * npc_sell_item(16 bytes) uint a = BitConverter.ToUInt32(data, 0); int offset = 0; uint moneyGain = 0; uint itemCount = 0; const int itemSize = 16; // npc_sell_item: tid(4) index(4) count(4) price(4) bool formatA = ((data.Length - 4) >= 0) && (((data.Length - 4) % itemSize) == 0) && (a == (uint)((data.Length - 4) / itemSize)); if (formatA) { itemCount = a; offset = 4; } else if (data.Length >= 8) { uint b = BitConverter.ToUInt32(data, 4); bool formatB = ((data.Length - 8) >= 0) && (((data.Length - 8) % itemSize) == 0) && (b == (uint)((data.Length - 8) / itemSize)); if (formatB) { moneyGain = a; itemCount = b; offset = 8; } } // If we can't parse, fall back to server refresh. if (itemCount == 0 || offset == 0) { UnityGameSession.RequestInventoryAsync(0, () => { var ui = UnityEngine.Object.FindFirstObjectByType(); ui?.RefreshAll(); }); return; } long computedGain = moneyGain; var pPack = GetPack(EC_Inventory.Inventory_type.IVTRTYPE_PACK); if (pPack == null) return; for (int i = 0; i < itemCount; i++) { int baseIndex = offset + i * itemSize; if (baseIndex + itemSize > data.Length) break; int tid = BitConverter.ToInt32(data, baseIndex); uint invIndex = BitConverter.ToUInt32(data, baseIndex + 4); uint count = BitConverter.ToUInt32(data, baseIndex + 8); int price = BitConverter.ToInt32(data, baseIndex + 12); if (invIndex >= (uint)pPack.GetSize()) continue; if (count > 0) pPack.RemoveItem((int)invIndex, (int)count); // If server didn't include a total moneyGain, sum per-item prices. if (moneyGain == 0 && price > 0) computedGain += price; var remain = pPack.GetItem((int)invIndex, false); if (remain != null && remain.m_tid != tid) UnityGameSession.c2s_CmdGetItemInfo(0, (byte)invIndex); } if (computedGain > 0) AddMoneyAmount((int)Mathf.Min(int.MaxValue, computedGain)); var invUi = UnityEngine.Object.FindFirstObjectByType(); invUi?.RefreshAll(); UpdateEquipSkins(); } private bool IsInChariot() { CECGameRun pRun = EC_Game.GetGameRun(); if(pRun == null) return false; CECHostPlayer pHost = pRun.GetHostPlayer(); if (pHost && pHost.GetBattleInfo().IsChariotWar() && GetShapeType() == (int)PLAYERMODEL_TYPE.PLAYERMODEL_DUMMYTYPE2 && GetDummyModel((int)PLAYERMODEL_TYPE.PLAYERMODEL_DUMMYTYPE2)) { return true; } return false; } private BATTLEINFO m_BattleInfo; private BATTLEINFO GetBattleInfo() { return m_BattleInfo; } private void NotifyUIUpdateTeam(bool showDialog = false) { //try //{ // var ui = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan(); // var ui = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan(); // if (ui is CECGameUIMan gui) // gui.UpdateTeam(false); //} //catch { } var uiMan = CECUIManager.Instance?.GetInGameUIMan(); if (uiMan == null) return; // CECGameUIMan will forward to DlgTeamMain if it's open, otherwise do nothing. var dlgTeamMain = uiMan.GetDialog("Win_TeamMain") as DlgTeamMain; if (dlgTeamMain != null) { if (showDialog) { dlgTeamMain.ShowTeamDialog(); } else { dlgTeamMain.UpdateTeamInfo(); } } else { BMLogger.LogWarning("[CECHostPlayer] NotifyUIUpdateTeam: Failed to show DlgTeamMain"); } } /// Update host duel state from S2C duel commands (MSG_PM_DUELOPT). private void OnMsgHstDuelOpt(ECMSG Msg) { int cmdId = Convert.ToInt32(Msg.dwParam2); byte[] data = Msg.dwParam1 as byte[]; int idOpp = 0; if (data != null && data.Length >= 4) idOpp = BitConverter.ToInt32(data, 0); switch (cmdId) { case CommandID.DUEL_RECV_REQUEST: // Duel invite: show accept/reject popup (origin MSG_PM_DUELOPT with command DUEL_RECV_REQUEST = 214) if (idOpp != 0) { // CECUIManager.Instance?.ShowMessageBox( // title: "", // message: "Bạn vừa nhận được lời thách đấu. Bạn có chấp nhận không?", // messageBoxType: MessageBoxType.BothYesNoButton, // onClickedYes: () => UnityGameSession.c2s_CmdDuelReply(true, idOpp), // onClickedNo: () => UnityGameSession.c2s_CmdDuelReply(false, idOpp)); CECUIManager.Instance?.ShowMessageBoxYesAndNo("", "Bạn vừa nhận được lời thách đấu. Bạn có chấp nhận không?", null, ()=>UnityGameSession.c2s_CmdDuelReply(true, idOpp), () => UnityGameSession.c2s_CmdDuelReply(false, idOpp)); } break; case CommandID.DUEL_PREPARE: m_pvp.iDuelState = Duel_state.DUEL_ST_PREPARE; m_pvp.idDuelOpp = idOpp; m_pvp.iDuelTimeCnt = data != null && data.Length >= 8 ? BitConverter.ToInt32(data, 4) : 3000; break; case CommandID.HOST_DUEL_START: m_pvp.iDuelState = Duel_state.DUEL_ST_INDUEL; m_pvp.idDuelOpp = idOpp; m_pvp.iDuelTimeCnt = 0; // Origin: server must be notified of force-attack (PVP) so it accepts attacks on the duel opponent NotifyServerForceAttack(true); break; case CommandID.DUEL_STOP: case CommandID.DUEL_RESULT: m_pvp.iDuelState = Duel_state.DUEL_ST_STOPPING; m_pvp.iDuelTimeCnt = data != null && data.Length >= 8 ? BitConverter.ToInt32(data, 4) : 3000; if (data != null && data.Length >= 12) m_pvp.iDuelRlt = BitConverter.ToInt32(data, 8); NotifyServerForceAttack(false); break; case CommandID.DUEL_CANCEL: case CommandID.DUEL_REJECT_REQUEST: m_pvp.iDuelState = Duel_state.DUEL_ST_NONE; m_pvp.idDuelOpp = 0; m_pvp.iDuelTimeCnt = 0; NotifyServerForceAttack(false); break; } } /// Host received a team invite (MSG_HST_TEAMINVITE). Payload is cmd_team_leader_invite: idLeader, seq, pickFlag. Shows accept/reject message box and sends reply. private void OnMsgHstTeamInvite(ECMSG Msg) { byte[] data = Msg.dwParam1 as byte[]; int idLeader = 0; int team_seq = 0; if (data != null && data.Length >= 8) { idLeader = BitConverter.ToInt32(data, 0); team_seq = BitConverter.ToInt32(data, 4); } else if (data != null && data.Length >= 4) { idLeader = BitConverter.ToInt32(data, 0); } if (idLeader == 0) return; int seqCapture = team_seq; // CECUIManager.Instance?.ShowMessageBox( // title: "", // message: "Bạn đã nhận được lời mời tham gia tổ đội. Bạn có chấp nhận không?", // messageBoxType: MessageBoxType.BothYesNoButton, // onClickedYes: () => UnityGameSession.c2s_CmdTeamAgreeInvite(idLeader, seqCapture), // onClickedNo: () => UnityGameSession.c2s_CmdTeamRejectInvite(idLeader)); CECUIManager.Instance?.ShowMessageBoxYesAndNo("", "Bạn đã nhận được lời mời tham gia tổ đội. Bạn có chấp nhận không?",null, ()=>UnityGameSession.c2s_CmdTeamAgreeInvite(idLeader, seqCapture), () => UnityGameSession.c2s_CmdTeamRejectInvite(idLeader)); } /// Called when MSG_PM_PLAYERDUELOPT (229) is received; server may send duel start to both participants. If host is one of the two ids, set duel state. public void OnMsgPlayerDuelStart(byte[] data) { if (data == null || data.Length < 8) return; int id1 = BitConverter.ToInt32(data, 0); int id2 = BitConverter.ToInt32(data, 4); int cid = m_PlayerInfo.cid; if (cid == id1) { m_pvp.iDuelState = Duel_state.DUEL_ST_INDUEL; m_pvp.idDuelOpp = id2; m_pvp.iDuelTimeCnt = 0; NotifyServerForceAttack(true); } else if (cid == id2) { m_pvp.iDuelState = Duel_state.DUEL_ST_INDUEL; m_pvp.idDuelOpp = id1; m_pvp.iDuelTimeCnt = 0; NotifyServerForceAttack(true); } } #if UNITY_EDITOR /// /// Cycles through learned skills by removing all shortcuts and adding 2 new skills to slots 0 and 1. /// If m_startingSkillID is set (>0), uses that specific skill ID and the next one (ID+1). /// Otherwise, cycles through all learned skills in pairs. /// #endif public bool HostIsReady() { return m_bEnterGame; } void SetLevel2(int level2, bool bFirstTime) { int lastLevel2 = m_BasicProps.iLevel2; m_BasicProps.iLevel2 = level2; /*if (CanPlayTaoistEffect(lastLevel2, level2, bFirstTime)){ PlayTaoistEffect(); }*/ } /* public override bool IsWorkMoveRunning() { return m_pWorkMan.IsMovingToPosition(); }*/ public bool IsPosCollideFree(A3DVECTOR3 vTargetPos) { bool bAvailable = (false); while (true) { A3DVECTOR3 refake = new A3DVECTOR3(); float terrianHeight = CECGameRun.Instance.GetWorld().GetTerrainHeight(vTargetPos, ref refake); if (terrianHeight > vTargetPos.y + 1E-4f) break; A3DVECTOR3 vExt = m_CDRInfo.vExtent; env_trace_t trcInfo = default; trcInfo.dwCheckFlag = EC_CDR.CDR_EVN.CDR_BRUSH; trcInfo.vExt = vExt; trcInfo.vStart = vTargetPos + EC_Utility.ToA3DVECTOR3(g_vAxisY) * vExt.y; trcInfo.vDelta = new A3DVECTOR3(0); if (EC_CDR.CollideWithEnv(ref trcInfo)) break; bAvailable = true; break; } return bAvailable; } private float CalcAABBOnCollidePos(A3DAABB aabbTarget) { // ¸ù¾Ý HostPlayer Óëij¶ÔÏó£¨ÈçNPC£© AABB£¬¼ÆËã AABB ³åÍ»ÁÙ½ç״̬ÏÂÁ½Õß¾àÀë (Compute the separation distance between host/player AABBs when they nearly collide) // ·µ»ØÖµÓ¦Êʵ±Ôö¼ÓÒ»¸öÔöÁ¿£¬¼´Àë³åͻλÖÃÉÔԶЩ (Return value should keep a slightly safer distance) float fAABBCollideDist = 0.0f; A3DAABB aabbHost = m_aabb; A3DVECTOR3 vHostHeight = new A3DVECTOR3(0.0f, aabbHost.Extents.y, 0.0f); A3DVECTOR3 vHostRoot = aabbHost.Center - vHostHeight; A3DVECTOR3 vTargetHeight = new A3DVECTOR3(0.0f, aabbTarget.Extents.y, 0.0f); A3DVECTOR3 vTargetRoot = aabbTarget.Center - vTargetHeight; A3DVECTOR3 vRootDir = vTargetRoot - vHostRoot; float fRootDist = vRootDir.Normalize(); float fMinDist = Mathf.Min(Mathf.Min(aabbHost.Extents.x, aabbHost.Extents.y), aabbHost.Extents.z); fMinDist = Mathf.Max(fMinDist, 0.01f); if (fRootDist >= fMinDist) { A3DVECTOR3 vCenterDelta0 = aabbHost.Center - aabbTarget.Center; A3DVECTOR3 vTargetExt = aabbTarget.Extents; A3DVECTOR3 vHostExt = aabbHost.Extents; A3DVECTOR3 vSumExt = vTargetExt + vHostExt; A3DVECTOR3 t = new A3DVECTOR3(0.0f); const float fZero = 0.001f; if (Mathf.Abs(vRootDir.x) >= fZero) { float t1 = (vSumExt.x - vCenterDelta0.x) / vRootDir.x; float t2 = (vSumExt.x + vCenterDelta0.x) / -vRootDir.x; if (t1 >= fZero && t1 < fRootDist) t.x = t1; if (t2 >= fZero && t2 < fRootDist && t2 > t.x) t.x = t2; } if (Mathf.Abs(vRootDir.y) >= fZero) { float t1 = (vSumExt.y - vCenterDelta0.y) / vRootDir.y; float t2 = (vSumExt.y + vCenterDelta0.y) / -vRootDir.y; if (t1 >= fZero && t1 < fRootDist) t.y = t1; if (t2 >= fZero && t2 < fRootDist && t2 > t.y) t.y = t2; } if (Mathf.Abs(vRootDir.z) >= fZero) { float t1 = (vSumExt.z - vCenterDelta0.z) / vRootDir.z; float t2 = (vSumExt.z + vCenterDelta0.z) / -vRootDir.z; if (t1 >= fZero && t1 < fRootDist) t.z = t1; if (t2 >= fZero && t2 < fRootDist && t2 > t.z) t.z = t2; } float fHostMove = 0.0f; if (t.x > 0.0f) fHostMove = t.x; if (t.y > 0.0f && t.y > fHostMove) fHostMove = t.y; if (t.z > 0.0f && t.z > fHostMove) fHostMove = t.z; if (fHostMove > 0.0f) { fAABBCollideDist = fRootDist - fHostMove; } } return fAABBCollideDist; } private bool CalcCollideFreePos(A3DAABB aabbTarget, out A3DVECTOR3 vPos) { // ¸ù¾Ýµ±Ç° HostPlayer λÖÃÓë¸ø¶¨Ä¿±êµÄÅöײ°üΧºÐ aabbTarget£¬¼ÆËãÄ¿±ê¸½½ü(ÓëµØÐκÍ͹°ü)ÎÞ³åÍ»µÄλÖà (Find a terrain/brush free position near the target AABB) // vPos ´æ·ÅÎÞ³åÍ»µÄλÖ㬿ÉÓÃÓÚ SetPos (vPos stores the safe position for SetPos) bool bFound = false; vPos = default; A3DAABB aabbHost = m_aabb; A3DVECTOR3 vHostHeight = new A3DVECTOR3(0.0f, aabbHost.Extents.y, 0.0f); A3DVECTOR3 vHostRoot = aabbHost.Center - vHostHeight; A3DVECTOR3 vTargetHeight = new A3DVECTOR3(0.0f, aabbTarget.Extents.y, 0.0f); A3DVECTOR3 vTargetRoot = aabbTarget.Center - vTargetHeight; A3DVECTOR3 vRootDir = vTargetRoot - vHostRoot; float fRootDist = vRootDir.Normalize(); float fAABBCollideDist = CalcAABBOnCollidePos(aabbTarget); if (fAABBCollideDist > 0.0f) { float[] fDeltaTests = { 0.001f, 0.1f }; foreach (float fDelta in fDeltaTests) { // ¿¼Âǵ½µØÐÎÆð·üµÈÒòËØ£¬Óëʵ¼Ê³åͻλÖÃÀ­¿ªÒ»¶¨¾àÀë²¢Öð¸ö²âÊÔ£¬ÒÔÔö´ó³É¹¦·µ»Ø¼¸ÂÊ (Offset slightly to improve success probability) if (fRootDist > fAABBCollideDist + fDelta) { float fTestMoveDist = fRootDist - (fAABBCollideDist + fDelta); A3DVECTOR3 vTestPos = vHostRoot + vRootDir * fTestMoveDist; // ¸ù¾ÝµØÐκÍ͹°ü½øÐÐÐÞÕý¡¢²¢²âÊÔ¿ÉÓÃÐÔ (Adjust with terrain/brush constraints and verify) vTestPos.y = ClampAboveGround(vTestPos); if (IsPosCollideFree(vTestPos)) { // ºÍ͹°üÎÞÅöײ£¬Ö±½Ó¿ÉÓà (No brush collision, use directly) bFound = true; vPos = vTestPos; } else { // ºÍ͹°üÓÐÅöײ£¬³¢ÊÔÔÚÊúÖ±·½ÏòÉϲéÕÒ (If still colliding, search vertically) if (CalcVerticalCollideFreePos(vTestPos, out A3DVECTOR3 vTestPos2)) { bFound = true; vPos = vTestPos2; } } if (bFound) { break; } } } } return bFound; } private bool CalcBrushOnCollidePos(A3DVECTOR3 vTestPos, A3DVECTOR3 vDelta, A3DVECTOR3 vExtents, out A3DVECTOR3 vPos, out bool bNoCollide) { // ·µ»Ø true£ºvPos ΪÎÞÅöײλÖã»Èô bNoCollide Ϊ true£¬Ôò vPos Ϊ vTestPos£»·ñÔòΪ¼ÆËã³öµÄλÖà (Return true with vPos pointing at a usable location; if bNoCollide==true the input position was already free) // ·µ»Ø false£ºÒâζ×Å bStartSolid = true (Return false implies we started within solid geometry) bool bFound = false; A3DVECTOR3 vCenterHeight = new A3DVECTOR3(0.0f, vExtents.y, 0.0f); env_trace_t trcInfo = new env_trace_t { dwCheckFlag = EC_CDR.CDR_EVN.CDR_BRUSH, vExt = vExtents, vStart = vTestPos + vCenterHeight, vDelta = vDelta, vTerStart = vTestPos + vCenterHeight, vWatStart = vTestPos + vCenterHeight, bWaterSolid = false }; if (EC_CDR.CollideWithEnv(ref trcInfo)) { bNoCollide = false; if (!trcInfo.bStartSolid) { vPos = trcInfo.vStart + trcInfo.fFraction * trcInfo.vDelta - vCenterHeight; bFound = true; } else { vPos = default; } } else { vPos = vTestPos; bNoCollide = true; bFound = true; } if (!bFound) { vPos = default; bNoCollide = false; } return bFound; } private bool CalcVerticalCollideFreePos(A3DVECTOR3 vRefPos, out A3DVECTOR3 vPos) { // ¼ÆËã vRefPos ¸½½üÊúÖ±·½ÏòÉÏÓë͹°ü¼°µØÐÎÎÞ³åÍ»µÄλÖà (Search along the vertical direction around vRefPos for a collision-free spot) // vRefPos Ϊ HostPlayer foot λÖà (vRefPos corresponds to the host's foot) bool bFound = false; vPos = default; CECWorld world = CECGameRun.Instance.GetWorld(); if (world == null) { return false; } while (true) { A3DAABB hostAabb = m_aabb; A3DVECTOR3 vHostExts = hostAabb.Extents; // ¹¹½¨ËõС°æ HostPlayer £¬ÏòÏÂѰÕÒ¿ÉÓÃλÖà (Use a shrinked collider to probe downward) A3DVECTOR3 vShrinkExts = new A3DVECTOR3(vHostExts.x, vHostExts.y * 0.5f, vHostExts.z); A3DVECTOR3 vShrinkPos = vRefPos + new A3DVECTOR3(0.0f, vHostExts.y * 0.5f, 0.0f); A3DVECTOR3 vStartPos = vShrinkPos; A3DVECTOR3 vVerticalDelta = new A3DVECTOR3(0.0f, -1.0f, 0.0f); A3DVECTOR3 dummyNormal = default; float terrainHeight = world.GetTerrainHeight(vStartPos, ref dummyNormal); if (vStartPos.y < terrainHeight + 0.01f) { // ²âÊÔλÖÃÐè´¦ÓÚµØÐÎÒÔÉÏ (Candidate must be above the terrain) break; } if (!CalcBrushOnCollidePos(vStartPos, vVerticalDelta, vShrinkExts, out A3DVECTOR3 vCandidate, out bool bNoCollide)) { // vStartPos ´¦ÓÚ͹°üÀï (Start position lies inside a brush) break; } if (bNoCollide) { // ´Ó vStartPos µ½ vRefPos ¶¼ÎÞÅöײ£¬¿ÉÄÜÊÇ (No collision between vStartPos and vRefPos; possible cases) // ÇéÐÎ1£ºvRefPos ´¦Í·¶¥ÓÐ͹°üµ¼ÖÂÅöײ£¨´Ó¶øµ÷Óô˺¯ÊýÐÞÕý£© (Case1: upper brush causes the issue) // ÇéÐÎ2£º»òÕß vRefPos ±¾Éí¼´ÎÞÅöײ£¬Îóµ÷Óô˺¯Êý×öÖØ¸´¼ÆËã (Case2: vRefPos was already free) // ÇéÐÎ3£º»òÆäËüδ¿¼ÂÇÇé¿ö (Case3: other edge cases) // ³¢ÊԴӵײ¿Ïò vRefPos ʹÓÃԭʼ´óС HostPlayer ²éÕÒÎÞÅöײλÖã¬ÒÔ´¦ÀíÉÏÊöÇéÐÎ1 (Try again with the original extents to address case1) vStartPos += vVerticalDelta; vVerticalDelta = vRefPos - vStartPos; if (!CalcBrushOnCollidePos(vStartPos, vVerticalDelta, vHostExts, out vCandidate, out bNoCollide)) { break; } if (bNoCollide) { vCandidate = vRefPos; } // else vCandidate ÊÇËõС°æ HostPlayer ÎÞÅöײµÄλÖã¬Ò²ÊÇʵ¼Ê´óС HostPlayer ÐÞÕý vRefPos µÄλÖà (Otherwise the computed candidate already fixes the offset) } vCandidate.y = ClampAboveGround(vCandidate); if (!IsPosCollideFree(vCandidate)) { // λÖóåÍ» (Still interpenetrating) break; } vPos = vCandidate; bFound = true; break; } return bFound; } private float ClampAboveGround(A3DVECTOR3 vPos) { // ½« vPos ÏÞÖÆµ½µØÃæÒÔÉÏ (Clamp vPos above the ground) // ·ÉÐÐ״̬»òË®µ×ʱ£¬ÏÞÖÆÈËÎïÔÚË®ÃæÒÔÉÏ£¬ÀëË®Ãæ/µØÃæÒ»¶¨¾àÀ룻Èôµ÷Õûºó vPos ÊúÖ±ÍùÏÂÓÐ͹°ü£¬Ôò»¹»áÏÞÖÆµ½Àë͹°üÒ»¶¨¾àÀë (When flying/underwater we also keep distance from surfaces and nearby brushes) // ×¢Ò⣺vPos ±»µ÷Õûºó£¬ÓпÉÄÜ´¦ÓÚ͹°üÖУ»Ðè¼ì²é·µ»Ø¸ß¶Èµ÷ÕûÖµ£¬ÒÔ±ÜÃâµ÷Õû¹ý´ó (Beware of large adjustments placing us back into brushes) A3DVECTOR3 vTemp = new A3DVECTOR3(vPos); CECWorld world = CECGameRun.Instance.GetWorld(); if (world == null) { return vTemp.y; } while (true) { A3DVECTOR3 dummyNormal = default; float fTerrainHeight = world.GetTerrainHeight(vTemp, ref dummyNormal); vTemp.y = EC_Utility.a_ClampFloor(vTemp.y, fTerrainHeight); A3DAABB hostAabb = m_aabb; A3DVECTOR3 vExts = hostAabb.Extents; if (IsFlying()) { float fAbove = m_MoveConst.fMinAirHei; // Ïȱ£Ö¤ÀëµØÃæ/Ë®ÃæÒ»¶¨¸ß¶È (Keep some height over terrain/water) float fWaterHeight = world.GetWaterHeight(vTemp); float fSurface = Mathf.Max(fTerrainHeight, fWaterHeight); vTemp.y = EC_Utility.a_ClampFloor(vTemp.y, fSurface + fAbove); // ÔÙ³¢ÊÔÀë͹°üÒ»¶¨¸ß¶È (Then test against brushes) if (!CalcBrushOnCollidePos(vTemp, GPDataTypeHelper.g_vAxisY * (fSurface - vTemp.y), vExts, out A3DVECTOR3 vHitPos, out bool bNoCollide) || bNoCollide) { break; } // ÉèÖÃÀë͹°üÒ»¶¨¸ß¶È (Clamp to stay above brushes) vTemp.y = EC_Utility.a_ClampFloor(vTemp.y, vHitPos.y + fAbove); break; } float fWaterHeightLow = world.GetWaterHeight(vTemp); if (fWaterHeightLow > fTerrainHeight && vTemp.y + vExts.y < fWaterHeightLow - m_MoveConst.fWaterSurf) { // Ë®µ× (Underwater) float fAbove = m_MoveConst.fMinWaterHei; // Ïȱ£Ö¤ÀëË®µ×Ò»¶¨¾àÀë (Keep distance from the riverbed) vTemp.y = EC_Utility.a_ClampFloor(vTemp.y, fTerrainHeight + fAbove); // ÔÙ³¢ÊÔÀë͹°üÒ»¶¨¸ß¶È (Then ensure clearance from brushes) if (!CalcBrushOnCollidePos(vTemp, GPDataTypeHelper.g_vAxisY * (fTerrainHeight - vTemp.y), vExts, out A3DVECTOR3 vHitPos, out bool bNoCollide) || bNoCollide) { break; } // ÉèÖÃÀë͹°üÒ»¶¨¸ß¶È (Clamp relative to brush hit position) vTemp.y = EC_Utility.a_ClampFloor(vTemp.y, vHitPos.y + fAbove); break; } // µØÃæÉÏ£¬ÎÞÐèÔÙ´¦Àí (Already safe on ground) break; } return vTemp.y; } public void HandleRevive(short sReviveType, A3DVECTOR3 pos) { // Move to revive position and play revive animation PlayAction((int)PLAYER_ACTION_TYPE.ACT_REVIVE); // Clear any running dead work if exists m_pWorkMan?.FinishRunningWork(CECHPWork.Host_work_ID.WORK_DEAD); // Clear corpse state so player is alive again m_dwStates &= ~(uint)PlayerNPCState.GP_STATE_CORPSE; UnityGameSession.c2s_CmdGetAllData(true, true, false); UnityGameSession.c2s_CmdSendEnterPKPrecinct(); UnityGameSession.RequesrQueryPlayerCash(); } // Message MSG_HST_SELTARGET handler void OnMsgHstSelTarget(ECMSG Msg) { //BMLogger.LogError("HoangDev: OnMsgHstSelTarget"); if (Convert.ToInt32(Msg.dwParam2) == CommandID.SELECT_TARGET) { var data = (byte[])Msg.dwParam1; cmd_select_target pCmd = GPDataTypeHelper.FromBytes(data); // In duel: don't let server force selection back to duel opponent when player chose another target if (IsInDuel() && pCmd.idTarget == m_pvp.idDuelOpp && m_idSelTarget != 0 && m_idSelTarget != m_pvp.idDuelOpp) return; m_idSelTarget = pCmd.idTarget; m_idUCSelTarget = 0; } else if (Convert.ToInt32(Msg.dwParam2) == CommandID.UNSELECT) { m_idSelTarget = 0; } } public override void SetUpPlayer() { base.SetUpPlayer(); m_iCID = (int)CECObject.Class_ID.OCID_HOSTPLAYER; m_IncantCnt = new CECCounter(); m_IncantCnt.SetPeriod(1000); m_IncantCnt.Reset(true); m_bEnterGame = false; } public async Task InitCharacter(cmd_self_info_1 role) { SetUpPlayer(); m_dwStates = (uint)role.state; controller = GetComponent(); if (!controller) { BMLogger.LogError("HostPlayer InitCharacter no CharacterController"); } //if (role.name != null && role.name.ByteArray != null) //{ // roleName = Encoding.UTF8.GetString(role.name.ByteArray, 0, role.name.Length); //} SetPlayerInfor(new INFO(role.cid, role.crc_e, role.crc_c)); await LoadResources(); isDataAwaitToReady = true; Vector3 pos = new Vector3(role.pos.x, role.pos.y, role.pos.z); string roleName = Encoding.Unicode.GetString(UnityGameSession.Instance.GetRoleInfo().name.ByteArray); SetPlayerName(roleName); if (txtName != null) txtName.text = roleName; EventBus.Publish(new InfoHostPlayer(roleName)); playerTransform.position = pos; m_dwResFlags = (uint)PlayerResourcesReadyFlag.RESFG_ALL; joystick = FindAnyObjectByType(); EventBus.Subscribe(JoystickRelease); EventBus.Subscribe(OnClickJoystick); if (TryGetComponent(out var visual)) { visual.InitPlayerEventDoneHandler(); } m_aabb.Center = GPDataTypeHelper.g_vOrigin; m_aabb.Extents.Set(0.3f, 0.9f, 0.3f); m_aabbServer = m_aabb; m_MoveConst.fStepHei = 0.8f; m_MoveConst.fMinAirHei = 1.6f; m_MoveConst.fMinWaterHei = 0.3f; m_MoveConst.fShoreDepth = 1.6f; m_MoveConst.fWaterSurf = 0.6f; CalcPlayerAABB(); SetPos(pos); m_MoveCtrl.SetHostLastPos(EC_Utility.ToA3DVECTOR3(pos)); m_MoveCtrl.SetLastSevPos(EC_Utility.ToA3DVECTOR3(pos)); //m_CDRInfo.vTPNormal = GroundCheck(out RaycastHit hit) ? hit.normal : Vector3.zero; m_CDRInfo.vExtent = m_aabbServer.Extents; Vector3 pStart = pos; pos.y += m_CDRInfo.vExtent.y; if (Physics.RaycastNonAlloc(pos, Vector3.down, hits, m_CDRInfo.vExtent.y, 1 << 6) > 0) { System.Array.Sort(hits, 0, hits.Length, raycastHitDistanceComparer); m_CDRInfo.vTPNormal = EC_Utility.ToA3DVECTOR3(hits[0].normal); } else { m_CDRInfo.vTPNormal = g_vOrigin; } m_CDRInfo.fYVel = 0.0f; m_CDRInfo.fSlopeThresh = EC_SLOPE_Y; m_CDRInfo.fStepHeight = m_MoveConst.fStepHei; m_AirCDRInfo.vExtent = m_aabbServer.Extents; m_AirCDRInfo.fUnderWaterDistThresh = m_MoveConst.fWaterSurf; // Create work manager m_pWorkMan = new CECHPWorkMan(this); m_pWorkMan.StartWork_p0(m_pWorkMan.CreateWork(Host_work_ID.WORK_STAND)); if (IsDead()) { //CECHPWorkDead pWork = (CECHPWorkDead*)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_DEAD); //pWork.SetBeDeadFlag(true); //m_pWorkMan.StartWork_p0(pWork); EventBus.PublishChannel(GetCharacterID(), new ClearComActFlagAllRankNodesEvent(true)); PlayAction((int)PLAYER_ACTION_TYPE.ACT_GROUNDDIE); PopupManager.NotifyPlayerDied(); } else { PopupManager.NotifyPlayerRevived(); UnityGameSession.c2s_CmdGetAllData(true, true, false); UnityGameSession.c2s_CmdSendEnterPKPrecinct(); UnityGameSession.RequesrQueryPlayerCash(); } /* else if (IsSitting()) { CECHPWorkSit* pWork = (CECHPWorkSit*)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_SIT); pWork.SetBeSittingFlag(true); m_pWorkMan.StartWork_p1(pWork); }*/ //m_GndInfo.bOnGround = GroundCheck(out lastGroundHit); m_pPetCorral = new CECPetCorral(); if (m_pWorkMan == null) { return; } LoadGfx(); } public async void LoadGfx() { // Load GFX var gfxCaster = EC_Game.GetGFXCaster(); // m_pMoveTargetGFX = g_pGame.GetGFXCaster().LoadGFXEx(res_GFXFile(RES_GFX_MOVETARGET)); m_pSelectedGFX = await gfxCaster.LoadGFXEx(EC_Resource.res_GFXFile((int)GfxResourceType.RES_GFX_CURSORHOVER)); m_pHoverGFX = await gfxCaster.LoadGFXEx(EC_Resource.res_GFXFile((int)GfxResourceType.RES_GFX_CURSORHOVER)); // m_pFloatDust = g_pGame.GetGFXCaster().LoadGFXEx(res_GFXFile(RES_GFX_FLOATING_DUST)); if (true /*CECUIConfig::Instance().GetGameUI().bEnableActionSwitch*/) { m_pActionSwitcher = new CECActionSwitcher(this); } else m_pActionSwitcher = new CECActionSwitcherBase(this); // TODO: Move this to right flow later , it's just for test now } private void OnMsgHstPushMove(JoystickPressEvent joystickPressEvent) { //_playerStateMachine.ChangeState(_moveState); /* if (m_pWorkMan.IsSitting()) { g_pGame.GetGameSession().c2s_CmdStandUp(); return; }*/ m_dwMoveRelDir = 0; if (!CanDo(ActionCanDo.CANDO_MOVETO)) return; if (joystick.Vertical > 0) { if (joystick.Horizontal > 0) { m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_FORWARD | MOVE_DIR.MD_RIGHT); } else if (joystick.Horizontal < 0) { m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_FORWARD | MOVE_DIR.MD_LEFT); } else { m_dwMoveRelDir |= (uint)MOVE_DIR.MD_FORWARD; } } else if (joystick.Vertical < 0) { if (joystick.Horizontal > 0) { m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_BACK | MOVE_DIR.MD_RIGHT); } else if (joystick.Horizontal < 0) { m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_BACK | MOVE_DIR.MD_LEFT); } else { m_dwMoveRelDir |= (uint)MOVE_DIR.MD_BACK; } } else { if (joystick.Horizontal > 0) { m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_RIGHT); } else if (joystick.Horizontal < 0) { m_dwMoveRelDir |= (uint)(MOVE_DIR.MD_LEFT); } } bool bPushMove = true; /* if (Msg.dwParam1 == 8 || Msg.dwParam1 == 9) { if (m_iMoveEnv != (int)MoveEnvironment.MOVEENV_AIR && m_iMoveEnv != (int)MoveEnvironment.MOVEENV_WATER) bPushMove = false; }*/ if (bPushMove /*&& !IsAboutToDie()*/ && CanDo(ActionCanDo.CANDO_MOVETO)) { if (m_pWorkMan.CanStartWork(Host_work_ID.WORK_MOVETOPOS)) { CECHPWorkMove pNewWork = (CECHPWorkMove)m_pWorkMan.CreateWork(Host_work_ID.WORK_MOVETOPOS); pNewWork.SetDestination(CECHPWorkMove.DestTypes.DEST_PUSH, g_vOrigin); m_pWorkMan.StartWork_p1(pNewWork); } } } private void OnDestroy() { EventBus.Unsubscribe(JoystickRelease); EventBus.Unsubscribe(OnClickJoystick); } //TODO: Remove this function. Since it has been deprecated. public void InitCharacter(info_player_1 role) { string roleName = "(Error decoding name)"; //if (role.name != null && role.name.ByteArray != null) //{ // roleName = Encoding.UTF8.GetString(role.name.ByteArray, 0, role.name.Length); //} Vector3 pos = new Vector3(role.pos.x, role.pos.y, role.pos.z); if (txtName != null) txtName.text = roleName; playerTransform.position = pos; // SetPlayerModel(); //Debug.LogError("Pos Character = " + pos); } /// Use host's m_pvp (we update it from S2C duel packets). Base IsInDuel() reads CECPlayer.m_pvp which is never set. public new bool IsInDuel() { return m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL; } /// Duel opponent character id when in duel; 0 otherwise. Used by HPWork so trace/melee send correct PVP mask. public int GetDuelOpponentId() { return m_pvp.idDuelOpp; } public void ClearAnimation() { EventBus.PublishChannel(GetCharacterID(), new ClearComActFlagAllRankNodesEvent(true)); } public CECActionSwitcherBase GetActionSwitcher() { return m_pActionSwitcher; } private float A3d_Magnitude(A3DVECTOR3 v) { return Mathf.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z); } public int GetCharacterID() { return m_PlayerInfo.cid; } public bool CanTouchTarget(A3DVECTOR3 vHostPos, A3DVECTOR3 vTargetPos, float fTargetRad, int iReason, float fMaxCut = 1.0f) { float fDist = A3d_Magnitude(vTargetPos - vHostPos); //bool bResult = false; float fRange = 0.0f; //string reasonStr = iReason == 1 ? "melee" : (iReason == 2 ? "cast magic" : (iReason == 3 ? "talk" : $"unknown({iReason})")); switch (iReason) { case 1: // melee { if (fMaxCut >= 0.0f) { float fCutDist = m_ExtProps.ak.AttackRange * 0.3f; if (fCutDist > fMaxCut) fCutDist = fMaxCut; fRange = m_ExtProps.ak.AttackRange - fCutDist; } else { fRange = m_ExtProps.ak.AttackRange * 0.7f; } if (fDist - fTargetRad <= fRange) { //bResult = true; /* BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " + $"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " + $"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " + $"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " + $"requiredRange={fRange:F2}, effectiveDistance={fDist - fTargetRad:F2}, " + $"result={bResult}, attackRange={m_ExtProps.ak.AttackRange:F2}");*/ return true; } break; } case 2: // cast magic { if (m_pPrepSkill != null) { //TODO : Check this function GetCastRange fRange = m_pPrepSkill.GetCastRange(m_ExtProps.ak.AttackRange, GetPrayDistancePlus()); if (fRange > 0.0f) { if (fDist - fTargetRad <= fRange) { //bResult = true; /* BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " + $"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " + $"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " + $"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " + $"requiredRange={fRange:F2}, effectiveDistance={fDist - fTargetRad:F2}, " + $"result={bResult}, skillID={m_pPrepSkill.GetSkillID()}, attackRange={m_ExtProps.ak.AttackRange:F2}");*/ return true; } } else { //bResult = true; /*BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " + $"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " + $"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " + $"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " + $"requiredRange=unlimited, result={bResult}, skillID={m_pPrepSkill.GetSkillID()}");*/ return true; } } break; } case 3: // talk { fRange = 5.0f; if (fDist - fTargetRad <= fRange) { //bResult = true; return true; } break; } default: // no special reason { fRange = (fTargetRad + m_fTouchRad) * 3.0f; if (fDist < fRange) { //bResult = true; return true; } break; } } // Log distance check result for debugging // 记录距离检查结果用于调试 /* BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " + $"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " + $"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " + $"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " + $"requiredRange={fRange:F2}, effectiveDistance={fDist - fTargetRad:F2}, " + $"result={bResult}, attackRange={m_ExtProps.ak.AttackRange:F2}");*/ return false; } public void RemoveObjectFromTabSels(CECObject pObject) { for (int i = 0; i < m_aTabSels.Count; i++) { if (m_aTabSels[i] == pObject) { m_aTabSels.RemoveAt(i); break; } } } public bool CanTouchTarget(A3DVECTOR3 vTargetPos, float fTargetRad, int iReason, float fMaxCut = 1.0f) { // Use server-tracked position instead of visual position for distance checks // This ensures distance checks use the correct position immediately after flashmove // 使用服务器跟踪的位置而不是视觉位置进行距离检查 // 这确保在闪移后立即使用正确的位置进行距离检查 A3DVECTOR3 vector = EC_Utility.ToA3DVECTOR3(m_MoveCtrl.GetLastSevPos()); return CanTouchTarget(vector, vTargetPos, fTargetRad, iReason, fMaxCut); } public bool IsRooting() { var mask = (uint)(Logic_Influence_Extned_states.LIES_ROOT | Logic_Influence_Extned_states.LIES_SLEEP | Logic_Influence_Extned_states.LIES_STUN); return (m_dwLIES & mask) != 0; } public bool IsInFortress() { return m_fortressEnter.role_in_war != 0; } bool IsPVPOpen() { return m_pvp.bEnable; } public float GetPrayDistancePlus() { return m_fPrayDistancePlus; } // Get faction ID //public int GetFactionID() //{ // return m_idFaction; //} public void SetPrayDistancePlus(float prayDistancePlus) { m_fPrayDistancePlus = prayDistancePlus; } public bool IsJumping() { return m_iJumpCount > 0; } public bool IsFalling() { return m_iMoveEnv == (int)MoveEnvironment.MOVEENV_GROUND && m_GndInfo.bOnGround == false; } public bool IsPlayingAction(int iAction) { if (iAction == (int)PLAYER_ACTION_TYPE.ACT_WALK) //&& _playerStateMachine.State is PlayerMoveState { return true; } if (iAction == (int)PLAYER_ACTION_TYPE.ACT_STAND) // && _playerStateMachine.State is PlayerIdleState { return true; } return false; } public void ResetJump() { m_iJumpCount = 0; m_bJumpInWater = false; } // Get move speed public float GetFlySpeed() { return m_ExtProps.mv.flight_speed; } public float GetSwimSpeed() { return m_ExtProps.mv.swim_speed; } public float GetSwimSpeedSev() { float fSpeedSev = GetSwimSpeed(); LayerMask layerGround = 1 << 6; LayerMask layerWater = 1 << 8; while (true) { if (!IsUnderWater()) break; CECWorld pWorld = EC_Game.GetGameRun().GetWorld(); if (pWorld == null) break; A3DVECTOR3 vPos = GetPos(); Vector3 startPoint = EC_Utility.ToVector3(vPos); Vector3 dir = EC_Utility.ToVector3(vPos) + Vector3.down; float fTerrainHeight = 0f; if (Physics.RaycastNonAlloc(startPoint, dir, hits, layerGround) > 0) { System.Array.Sort(hits, 0, hits.Length, raycastHitDistanceComparer); fTerrainHeight = Vector3.Distance(hits[0].point, startPoint); } float fWaterHeight = 0f; if (Physics.RaycastNonAlloc(startPoint, dir, hits, layerWater) > 0) { System.Array.Sort(hits, 0, hits.Length, raycastHitDistanceComparer); fWaterHeight = Vector3.Distance(hits[0].point, startPoint); } if (fWaterHeight <= fTerrainHeight) break; float fBorderLine = fWaterHeight - 2.0f; if (vPos.y <= fBorderLine) break; fSpeedSev = Math.Min(m_ExtProps.mv.run_speed, fSpeedSev); break; } return fSpeedSev; } public void PrepareNPCService(int idSev) { if (!GPDataTypeHelper.ISNPCID(m_idSevNPC)) { return; } DATA_TYPE DataType = new DATA_TYPE(); object pBuf = ElementDataManProvider.GetElementDataMan() .get_data_ptr((uint)idSev, ID_SPACE.ID_SPACE_ESSENCE, ref DataType); switch (DataType) { case DATA_TYPE.DT_NPC_TALK_SERVICE: break; case DATA_TYPE.DT_NPC_SELL_SERVICE: case DATA_TYPE.DT_NPC_BUY_SERVICE: { //// Get NPC's tex rate //float fScale = 1.0f; //CECNPC pNPC = EC_ManMessageMono.Instance._CECNPCMan.GetNPC(m_idSevNPC); //if (pNPC && pNPC.IsServerNPC()) //{ // CECNPCServer pServer = (CECNPCServer)pNPC; // fScale = (1.0f + pServer.GetTaxRate()) * pServer.GetPriceScale(); //} //// Fill NPC package //NPC_SELL_SERVICE pData = (NPC_SELL_SERVICE)pBuf; //int[] id_goods = new int[InventoryConst.IVTRSIZE_NPCPACK]; //for (int j = 0; j < InventoryConst.NUM_NPCIVTR; j++) //{ // for (int i = 0; i < InventoryConst.IVTRSIZE_NPCPACK; ++i) // id_goods[i] = (int)pData.pages[j].goods[i].id; // FillNPCPack(j, pData.pages[j].page_title, id_goods, fScale, false); //} //// Clear packs //m_pBuyPack.RemoveAllItems(); //m_pSellPack.RemoveAllItems(); break; } case DATA_TYPE.DT_NPC_SKILL_SERVICE: case DATA_TYPE.DT_NPC_PETLEARNSKILL_SERVICE: { //CECNPC pNPC = EC_ManMessageMono.Instance._CECNPCMan.GetNPC(m_idSevNPC); //if (!pNPC || !pNPC.IsServerNPC()) //{ // return; //} //if (DataType == DATA_TYPE.DT_NPC_SKILL_SERVICE) // ((CECNPCServer)pNPC).BuildSkillList(idSev); //else // ((CECNPCServer)pNPC).BuildPetSkillList(); break; } case DATA_TYPE.DT_NPC_REPAIR_SERVICE: case DATA_TYPE.DT_NPC_INSTALL_SERVICE: case DATA_TYPE.DT_NPC_UNINSTALL_SERVICE: case DATA_TYPE.DT_NPC_TASK_IN_SERVICE: case DATA_TYPE.DT_NPC_TASK_OUT_SERVICE: case DATA_TYPE.DT_NPC_TASK_MATTER_SERVICE: case DATA_TYPE.DT_NPC_HEAL_SERVICE: case DATA_TYPE.DT_NPC_TRANSMIT_SERVICE: case DATA_TYPE.DT_NPC_TRANSPORT_SERVICE: case DATA_TYPE.DT_NPC_PROXY_SERVICE: case DATA_TYPE.DT_NPC_STORAGE_SERVICE: case DATA_TYPE.DT_NPC_DECOMPOSE_SERVICE: case DATA_TYPE.DT_NPC_PETNAME_SERVICE: case DATA_TYPE.DT_NPC_PETFORGETSKILL_SERVICE: break; case DATA_TYPE.DT_NPC_MAKE_SERVICE: { //// Fill NPC package //NPC_MAKE_SERVICE pData = (NPC_MAKE_SERVICE)pBuf; //for (int j = 0; j < NUM_NPCIVTR; j++) // FillNPCPack(j, pData.pages[j].page_title, pData.pages[j].id_goods, 1.0f, true); //// Clear deal pack //m_pDealPack.RemoveAllItems(); break; } case DATA_TYPE.DT_NPC_RANDPROP_SERVICE: { //NPC_RANDPROP_SERVICE* pData = (NPC_RANDPROP_SERVICE*)pBuf; //elementdataman* pDataMan = g_pGame.GetElementDataMan(); //// Fill equip data into NPC pack //ASSERT(sizeof(pData.pages) / sizeof(pData.pages[0]) == NUM_NPCIVTR ); //for (int j = 0; j < NUM_NPCIVTR; j++) //{ // unsigned int id_recipe = pData.pages[j].id_recipe; // DATA_TYPE dt = DT_INVALID; // RANDPROP_ESSENCE* pRecipe = (RANDPROP_ESSENCE*)pDataMan.get_data_ptr(id_recipe, ID_SPACE_RECIPE, dt); // if (pRecipe && dt == DT_RANDPROP_ESSENCE) // { // FillNPCPack(j, pData.pages[j].page_title, (int*)pRecipe.equip_id, 1.0f, false); // } // else // { // // skip the invalid recipe id // GetNPCSevPack(j).RemoveAllItems(); // GetNPCSevPack(j).SetName(_AL("")); // } //} //// Clear deal pack //m_pDealPack.RemoveAllItems(); break; } } } private void LogInventoryPacket(string tag, byte[] buffer, int hostId) { if (buffer == null) return; int index = 0; if (buffer.Length < 6) { //LogInventoryRaw(tag, buffer); return; } byte byPackage = buffer[index++]; byte ivtrSize = buffer[index++]; uint contentLength = BitConverter.ToUInt32(buffer, index); index += 4; int remaining = buffer.Length - index; int contentBytes = remaining; if (contentLength < (uint)remaining) { contentBytes = (int)contentLength; } if (contentBytes > 0) { byte[] content = new byte[contentBytes]; Buffer.BlockCopy(buffer, index, content, 0, contentBytes); } int trailing = buffer.Length - (index + contentBytes); if (trailing > 0) { byte[] tail = new byte[trailing]; Buffer.BlockCopy(buffer, index + contentBytes, tail, 0, trailing); } } public bool NaturallyStopMoving() { // if (!m_MoveCtrl.IsStop()) if (!IsPlayerMoving()) return true; // Host has been stopped if (m_iMoveMode == (int)MoveMode.MOVE_FREEFALL || InSlidingState() || IsJumping()) return false; // Host couldn't stop naturally if (!m_pWorkMan.IsStanding()) { m_pWorkMan.FinishAllWork(true); } m_MoveCtrl.SendStopMoveCmd(); return true; } public A3DVECTOR3 GetDir() { // Return forward direction from transform return EC_Utility.ToA3DVECTOR3(transform.forward); } public A3DVECTOR3 A3d_RotatePosAroundY(A3DVECTOR3 vDir, float fAngle) { float cos = Mathf.Cos(fAngle); float sin = Mathf.Sin(fAngle); A3DVECTOR3 result = new A3DVECTOR3(); result.x = vDir.x * cos + vDir.z * sin; result.y = vDir.y; result.z = -vDir.x * sin + vDir.z * cos; return result; } bool IsTooNear(A3DVECTOR3 vTarget, ref float fNearDist) { // ¸ù¾Ý¿Õ¼äÇé¿ö£¬¼ÆËãÏà½ü 3D ¿Õ¼äÉÏÓ¦±£³ÖµÄ½ÏС¾àÀ룬±ÜÃâÒÆ¶¯ºÜ½üÉõÖÁÖØºÏµÄÇé¿ö // ·µ»Ø true ±íÃ÷µ±Ç°¾àÀëСÓÚ¼ÆËã³öµÄ×îС¾àÀë A3DVECTOR3 vPos = GetPos(); A3DVECTOR3 vMoveDir = vTarget - vPos; float fDist = vMoveDir.Magnitude(); float fTestDist = (0.0f); const float fTestDistH = 0.1f; const float fMoveDistH = 0.01f; // ±£Ö¤ fTestDistH > fMoveDistH£¬Ê¹¼ì²â¸üÓÐЧ float fDeltaXZ = vMoveDir.MagnitudeH(); if (fDeltaXZ > 0.001f && (MathF.Abs(vMoveDir.y) / fDeltaXZ) <= 50) { // tangent Öµ²»Ì«´ó // ÒÔ fTestDistH Ϊˮƽ¾àÀëÒªÇ󣬼ÆËã¿Õ¼äÉÏÓ¦±£³ÖµÄ¾àÀë fTestDist = fTestDistH * fDist / fDeltaXZ; // ÒÔ fMoveDistH Ϊˮƽ¾àÀëÒªÇ󣬼ÆËã¿Õ¼äÉÏÓ¦ÒÆ½üµÄ¾àÀë fNearDist = fMoveDistH * fDist / fDeltaXZ; } else { // tangent ÖµºÜ´ó£¬»ò vPos Óë vTarget ÖØºÏµÄÇé¿ö fTestDist = 0.5f; fNearDist = fDist > 0.01f ? 0.01f : 0.0f; } return (fDist <= fTestDist); } public bool UpdateEquipSkins() { int[] aNewEquips = new int[InventoryConst.IVTRSIZE_EQUIPPACK]; EC_IvtrItem pItem = null; for (int i = 0; i < InventoryConst.IVTRSIZE_EQUIPPACK; i++) { // Use host player's equipment inventory (per-instance CECInventory) var host = CECGameRun.Instance?.GetHostPlayer(); var equipInv = host?.IvtrEquipPack; pItem = equipInv?.GetItem(i, false); if (pItem != null) aNewEquips[i] = pItem.m_tid; } ShowEquipments(aNewEquips, true, true); return true; } public byte glb_BuildPVPMask(bool bForceAttack) { byte byMask = 0; if (bForceAttack) byMask |= (byte)PVPMask.GP_PVPMASK_FORCE; else { CECConfigs pConfigs = EC_Game.GetConfigs(); if (pConfigs.GetGameSettings().bAtk_Player) { byMask |= (byte)PVPMask.GP_PVPMASK_FORCE; if (pConfigs.GetGameSettings().bAtk_NoMafia) byMask |= (byte)PVPMask.GP_PVPMASK_NOMAFIA; if (pConfigs.GetGameSettings().bAtk_NoWhite) byMask |= (byte)PVPMask.GP_PVPMASK_NOWHITE; if (pConfigs.GetGameSettings().bAtk_NoAlliance) byMask |= (byte)PVPMask.GP_PVPMASK_NOALLIANCE; if (pConfigs.GetGameSettings().bAtk_NoForce) byMask |= (byte)PVPMask.GP_PVPMASK_NOFORCE; } } return byMask; } public bool SelectTarget(int idTarget) { bool bRet = false; bool canDo = CanDo(ActionCanDo.CANDO_CHANGESELECT); bool canselect = CanSelectTarget(idTarget); if (canDo && canselect) { bRet = true; if (idTarget == 0) { //BMLogger.LogError("HoangDev: HostPlayer Unsetlect npc"); UnityGameSession.c2s_CmdUnselect(); m_idSelTarget = 0; m_idUCSelTarget = 0; } else { //BMLogger.LogError("HoangDev: HostPlayer setlect npc"); UnityGameSession.c2s_CmdSelectTarget(idTarget); m_idSelTarget = idTarget; m_idUCSelTarget = idTarget; } } return bRet; } public int GetMaxLevelSofar() { return Math.Max(m_ReincarnationTome.max_level, m_BasicProps.iLevel); } public bool CanUseProjectile(CECIvtrArrow pArrow) { if (pArrow == null) return false; CECIvtrWeapon pWeapon = (CECIvtrWeapon)m_pEquipPack.GetItem((int)IndexOfIteminEquipmentInventory.EQUIPIVTR_WEAPON); if (pWeapon == null) return false; IVTR_ESSENCE_WEAPON we = pWeapon.GetEssence(); if (we.weapon_type != (int)WeaponType.WEAPONTYPE_RANGE) return false; IVTR_ESSENCE_ARROW ae = pArrow.GetEssence(); if (we.require_projectile != (int)pArrow.GetDBSubType().id || we.weapon_level < ae.iWeaponReqLow || we.weapon_level > ae.iWeaponReqHigh) return false; return true; } bool CanSelectTarget(int idTarget) { if (idTarget == 0 || idTarget == this.GetCharacterID()) { // 0 means unselect return true; } // Duel: always allow selecting the duel opponent so we can attack/cast (distance checked when trace runs) if (IsInDuel() && idTarget == m_pvp.idDuelOpp) return true; CECObject pTarget = null; if (GPDataTypeHelper.ISPLAYERID(idTarget)) { EC_ElsePlayer pElsePlayer = (EC_ManMessageMono.Instance.GetECManPlayer.GetPlayer(idTarget)) as EC_ElsePlayer; if (pElsePlayer != null) { if (CanSafelySelect(pElsePlayer)) { pTarget = pElsePlayer; } } } else if (GPDataTypeHelper.ISNPCID(idTarget)) { CECNPC pNPC = EC_ManMessageMono.Instance.CECNPCMan.GetNPC(idTarget); if (pNPC != null) { if (CanSafelySelect(pNPC) && !pNPC.IsDead()) { pTarget = pNPC; } } } return pTarget ? pTarget.IsSelectable() : false; } float SafelySelectDistance() { // ·þÎñÆ÷¶Ô SelectTarget ÓжîÍâ¾àÀëÏÞÖÆ£¬Èýά¾àÀë 150.0¡¢Ë®Æ½¾àÀë 125.0 ÒÔÉϵ쬶¼»áÎÞ·¨Ñ¡ÖÐ // »ùÓÚÒÔÉÏÔ­Òò£¬¿Í»§¶ËÑ¡Ôñ¶ÔÏó¡¢»òÕß¶ÔÒѾ­Ñ¡ÔñµÄ¶ÔÏ󣬶¼È·±£ÆäÔÚ´ËÏÞÖÆ·¶Î§ÄÚ£¬¼´Ñ¡ÔñʱʹÓýÏС¾àÀë¼ì²â return 100.0f; } public bool CanSafelySelectWith(float fDistanceToHostPlayer) { return fDistanceToHostPlayer <= SafelySelectDistance(); } bool CanSafelySelect(EC_ElsePlayer pElsePlayer) { // IsSkeletonReady() Ϊ true ʱ, GetDistToHost() ²ÅΪÓÐЧÊý¾Ý // !IsSkeletonReady() ʱ£¬Ò²ÔÊÐíʹÓã¬Ä¿µÄÊDZÜÃâδ¿¼Âǵ½µÄÒâÍâÇé¿ö // ÏÂͬ return pElsePlayer && ( /*!IsSkeletonReady() || */CanSafelySelectWith(pElsePlayer.GetDistToHost())); } bool CanSafelySelect(CECNPC pNPC) { return pNPC && ( /*!IsSkeletonReady() ||*/ CanSafelySelectWith(pNPC.GetDistToHost())); } // Check whether host can do a behavior / 检查宿主是否可以执行某个行为 public bool CanDo(int iThing) { bool bRet = true; switch (iThing) { case ActionCanDo.CANDO_SITDOWN: if (IsDead() || IsAboutToDie() || IsJumping() || IsTrading() || IsUsingTrashBox() || IsRooting() || IsReviving() || IsTalkingWithNPC() || IsChangingFace() || !m_GndInfo.bOnGround || GetBoothState() != 0 || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsRidingOnPet() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove()) bRet = false; break; case ActionCanDo.CANDO_MOVETO: if (IsDead() || IsSitting() || IsTrading() || IsUsingTrashBox() || IsRooting() || IsReviving() || IsTalkingWithNPC() || IsChangingFace() || IsUsingItem() || GetBoothState() != 0 || m_bHangerOn || IsOperatingPet() != 0 || IsRebuildingPet() || IsPassiveMove()) bRet = false; break; case ActionCanDo.CANDO_MELEE: if (IsDead() || IsSitting() || m_idSelTarget == 0 || m_idSelTarget == m_PlayerInfo.cid || IsJumping() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsTrading() || IsReviving() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || CannotAttack() || GetBoothState() != 0 || m_iBuddyId != 0 || IsRidingOnPet() || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()) bRet = false; break; case ActionCanDo.CANDO_ASSISTSEL: if (IsDead() || !GPDataTypeHelper.ISPLAYERID(m_idSelTarget) || m_idSelTarget == m_PlayerInfo.cid || m_pTeam == null || m_pTeam.GetMemberByID(m_idSelTarget) == null || m_iBuddyId != 0 || IsPassiveMove() || m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOCHANGESELECT]) bRet = false; break; case ActionCanDo.CANDO_FLY: if (IsDead() || IsRooting() || IsSitting() || IsTrading() || IsReviving() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 || IsFlashMoving() || (m_pWorkMan != null && m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan.Work_priority.PRIORITY_2)) || m_bHangerOn || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsRidingOnPet() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() || m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOFLY] /*|| m_BattleInfo.IsChariotWar()*/) bRet = false; break; case ActionCanDo.CANDO_PICKUP: case ActionCanDo.CANDO_GATHER: if (IsDead() || IsAboutToDie() || IsSitting() || IsTrading() || IsUsingTrashBox() || IsReviving() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 || GetBuddyState() == 1 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()) bRet = false; break; case ActionCanDo.CANDO_TRADE: if (IsDead() || IsAboutToDie() || IsSitting() || IsJumping() || IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || IsSpellingMagic() || GetBoothState() != 0 || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsInvisible() || IsPassiveMove()) bRet = false; break; case ActionCanDo.CANDO_PLAYPOSE: if (IsDead() || IsAboutToDie() || IsSitting() || IsJumping() || IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || IsSpellingMagic() || IsShapeChanged() || IsReviving() || m_iMoveEnv != (int)MoveEnvironment.MOVEENV_GROUND || GetBoothState() != 0 || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsRidingOnPet() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() /*|| m_BattleInfo.IsChariotWar()*/) bRet = false; break; case ActionCanDo.CANDO_SPELLMAGIC: if (IsDead() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() || IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 || m_iBuddyId != 0 || IsRidingOnPet() || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()) bRet = false; break; case ActionCanDo.CANDO_SUMMONPET: if (IsDead() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() || IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 || IsInvisible() || IsGMInvisible() || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove() /*|| m_BattleInfo.IsChariotWar()*/) bRet = false; break; case ActionCanDo.CANDO_REBUILDPET: if (IsDead() || GPDataTypeHelper.ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() || IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 || m_iBuddyId != 0 || IsInvisible() || IsGMInvisible() || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove() || IsPlayerMoving() /*|| m_BattleInfo.IsChariotWar()*/) bRet = false; break; case ActionCanDo.CANDO_USEITEM: if (IsAboutToDie() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 || IsPassiveMove() /*|| m_BattleInfo.IsChariotWar()*/) bRet = false; break; case ActionCanDo.CANDO_JUMP: if (IsDead() || m_iJumpCount >= MAX_JUMP_COUNT || // cannot jump more than one time if shape mode is type2 (IsJumping() && (GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2)) || IsJumpInWater() || m_iMoveEnv == (int)MoveEnvironment.MOVEENV_AIR || IsSitting() || IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() || IsGathering() || IsRooting() || GetBoothState() != 0 || m_bHangerOn || (IsJumping() && IsRidingOnPet()) || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove() /*|| m_BattleInfo.IsChariotWar()*/) bRet = false; break; case ActionCanDo.CANDO_FOLLOW: if (IsDead() || IsAboutToDie() || IsSitting() || IsMeleeing() || IsReviving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || IsSpellingMagic() || GetBoothState() != 0 || m_bHangerOn || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()) bRet = false; break; case ActionCanDo.CANDO_BOOTH: if (IsDead() || IsAboutToDie() || IsPlayerMoving() || IsSitting() || IsReviving() || IsMeleeing() || IsJumping() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || IsSpellingMagic() || IsFlying() || IsUnderWater() || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsRidingOnPet() || IsInvisible() || IsPassiveMove()) bRet = false; break; case ActionCanDo.CANDO_FLASHMOVE: if (IsDead() || IsAboutToDie() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsJumping() || IsFlashMoving() || IsFalling() || IsChangingFace() || GetBoothState() != 0 || IsTakingOff() || (m_pWorkMan != null && m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan.Work_priority.PRIORITY_2)) || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()) bRet = false; break; case ActionCanDo.CANDO_BINDBUDDY: if (IsDead() || IsAboutToDie() || IsJumping() || IsSitting() || IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() || IsGathering() || IsRooting() || GetBoothState() != 0 || (m_pWorkMan != null && !m_pWorkMan.IsStanding()) || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() || m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOBIND]) bRet = false; break; case ActionCanDo.CANDO_DUEL: if (IsDead() || IsAboutToDie() || IsSitting() || IsFighting() || IsTrading() || IsReviving() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 || m_iBuddyId != 0 || m_pvp.iDuelState != (int)DuelState.DUEL_ST_NONE || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()) bRet = false; break; case ActionCanDo.CANDO_CHANGESELECT: if (m_playerLimits[(int)PLAYER_LIMIT.PLAYER_LIMIT_NOCHANGESELECT]) bRet = false; break; case ActionCanDo.CANDO_SWITCH_PARALLEL_WORLD: if (IsDead() || IsAboutToDie() || IsJumping() || IsFighting() || IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() || IsGathering() || IsRooting() || GetBoothState() != 0 || m_iBuddyId != 0 || IsOperatingPet() != 0 || IsRebuildingPet() || IsUsingItem() || GetShapeType() == (int)PlayerModelType.PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove()) bRet = false; break; } return bRet; } public bool GetPushDir(ref Vector3 vPushDir, uint dwMask, float deltaTime) { vPushDir = Vector3.zero; if (joystick.Horizontal == 0 && joystick.Vertical == 0) { if (isPressMoveUp) { vPushDir = Vector3.up; } else if (isPressMoveDown) { vPushDir = Vector3.down; } return false; } if (m_iMoveEnv == Move_environment.MOVEENV_WATER || m_iMoveEnv == Move_environment.MOVEENV_AIR) { float angle = Vector2.Angle(new Vector2(joystick.Horizontal, joystick.Vertical), Vector2.up); angle *= joystick.Horizontal < 0 ? 1 : -1; Vector2 v2Cam = new Vector2(mainCam.transform.forward.x, mainCam.transform.forward.z); v2Cam = Quaternion.Euler(0, 0, angle) * v2Cam; v2Cam.Normalize(); vPushDir.x = v2Cam.x; vPushDir.y = ((transform.position + Vector3.up * m_CDRInfo.vExtent.y) - mainCam.transform.position).normalized.y; if (isPressMoveUp) { vPushDir.y = Math.Abs(vPushDir.y) * Time.deltaTime; } else if (isPressMoveDown) { vPushDir.y = -Math.Abs(vPushDir.y) * Time.deltaTime; } vPushDir.z = v2Cam.y; } else { float angle = Vector2.Angle(new Vector2(joystick.Horizontal, joystick.Vertical), Vector2.up); angle *= joystick.Horizontal < 0 ? 1 : -1; Vector2 v2Cam = new Vector2(mainCam.transform.forward.x, mainCam.transform.forward.z); v2Cam = Quaternion.Euler(0, 0, angle) * v2Cam; v2Cam.Normalize(); vPushDir.x = v2Cam.x; vPushDir.y = 0; vPushDir.z = v2Cam.y; } return true; } // Is under water bool CanTakeOffWater() { // TO DO: fix later //A3DVECTOR3 vPos = GetPos(); //if (vPos.y < EC_Game.GetGameRun().GetWorld().GetWaterHeight(vPos) - m_MoveConst.fShoreDepth) // return false; //else // return true; A3DVECTOR3 vPos = GetPos(); float h0 = 0f; int countHits0 = Physics.RaycastNonAlloc(EC_Utility.ToVector3(vPos) + Vector3.up * 500f, Vector3.down, hits, 1000f, 1 << 8); if (countHits0 > 0) { System.Array.Sort(hits, 0, hits.Length, raycastHitDistanceComparer); h0 = hits[0].point.y; } if (vPos.y < h0 - m_MoveConst.fShoreDepth) { return false; } else { return true; } } bool IsUsingItem() { return m_pWorkMan.IsUsingItem(); } bool IsPassiveMove() { return m_pWorkMan.IsPassiveMoving(); } // Is about to die / 是否即将死亡 bool IsAboutToDie() { return m_bAboutToDie; } // Is rebuilding pet / 是否正在重建宠物 bool IsRebuildingPet() { return m_bInRebuildPet; } // Is riding on pet / 是否骑乘宠物 bool IsRidingOnPet() { return m_RidingPet.id != 0; } // Is flash moving / 是否在闪移 bool IsFlashMoving() { if (m_pWorkMan == null) return false; return m_pWorkMan.IsFlashMoving(); } // Get buddy state / 获取伙伴状态 // return value: 0 = no buddy, 1 = has buddy, 2 = hanger on int GetBuddyState() { if (m_bHangerOn) return 2; if (m_iBuddyId != 0) return 1; return 0; } // Is invisible / 是否隐身 bool IsInvisible() { return (m_dwStates & (uint)PlayerNPCState.GP_STATE_INVISIBLE) != 0; } // Is GM invisible / 是否GM隐身 bool IsGMInvisible() { // GMF_INVISIBLE would be a constant, using bit check // GMF_INVISIBLE 将是一个常量,使用位检查 return (m_dwGMFlags & 0x01) != 0; // Assuming GMF_INVISIBLE = 0x01 } // Is shape changed / 是否形状已改变 bool IsShapeChanged() { return m_iShape != 0; } // Is taking off / 是否正在起飞 bool IsTakingOff() { if (m_pWorkMan == null) return false; return m_pWorkMan.IsFlyingOff(); } // Is flying / 是否在飞行 bool IsFlying() { return (m_dwStates & (uint)PlayerNPCState.GP_STATE_FLY) != 0; } //public void SetGroundInfoClient() //{ // isGrounded = GroundCheck(out lastGroundHit); // m_GndInfo.bOnGround = isGrounded; //} public void SetRotationHP(Vector3 dir) { transform.rotation = Quaternion.LookRotation(dir); } public void SetRotationHPWithTime(Vector3 dir, float time) { StartCoroutine(RotateToDir(transform, dir, time)); } IEnumerator RotateToDir(Transform target, Vector3 dir, float duration) { Quaternion startRot = target.rotation; Quaternion endRot = Quaternion.LookRotation(dir); float t = 0f; while (t < duration) { t += Time.deltaTime; target.rotation = Quaternion.Slerp(startRot, endRot, t / duration); yield return null; } target.rotation = endRot; } void SetJumpInWater(bool b) { m_bJumpInWater = b; } // Is host in sliding state (in the state, host is sliding on slope) ? public int GetProfession() { return m_iProfession; } public void OnAllInitDataReady() { if (IsDead()) { /* CECGameUIMan pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan(); pGameUI.PopupReviveDialog(true);*/ PopupManager.NotifyPlayerDied(); } m_bEnterGame = true; } public void SetSelectedTarget(int id) { if (m_idSelTarget != id) EventBus.Publish(new CECHostPlayer.TargetHUDClearEvent()); m_idSelTarget = id; // In duel, when player selects a different target, cancel trace work so it doesn't overwrite selection on touch if (IsInDuel() && id != 0 && id != m_pvp.idDuelOpp) m_pWorkMan?.FinishRunningWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT); // When selecting another player, publish NPCINFO so target HUD shows (server doesn't resend base info on select) if (id != 0 && id != GetCharacterID() && GPDataTypeHelper.ISPLAYERID(id)) { var elsePlayer = EC_ManMessageMono.Instance?.GetECManPlayer?.GetPlayer(id) as EC_ElsePlayer; if (elsePlayer != null && elsePlayer.m_bBaseInfoReady) { string name = elsePlayer.GetName(); if (!string.IsNullOrEmpty(name)) EventBus.Publish(new CECHostPlayer.NPCINFO(name, 100, 100, id)); } } } public new int GetSelectedTarget() { return m_idSelTarget; } // Auto select a attackable target — cycle by distance (nearest → farthest, then repeat) // 自动选择可攻击目标 — 按距离循环(从近到远,再回到最近) public int AutoSelectTarget() { if (!CanDo(ActionCanDo.CANDO_CHANGESELECT)) return 0; int idCurSel = (m_idSelTarget != 0 && m_idSelTarget != m_PlayerInfo.cid) ? m_idSelTarget : 0; // idCurSel == 0 so TabSelectCandidates includes current target for index-based cycling List aCandPlayers = new List(); EC_ManMessageMono.Instance.EC_ManPlayer.TabSelectCandidates(0, aCandPlayers); List aCandNPCs = new List(); EC_ManMessageMono.Instance.CECNPCMan.TabSelectCandidates(0, aCandNPCs); var sorted = new List<(float dist, int id, CECObject obj)>(); foreach (var pPlayer in aCandPlayers) { if (pPlayer == null) continue; sorted.Add((pPlayer.GetDistToHost(), pPlayer.GetCharacterID(), pPlayer)); } foreach (var pNPC in aCandNPCs) { if (pNPC == null) continue; sorted.Add((pNPC.GetDistToHost(), pNPC.GetNPCID(), pNPC)); } sorted.Sort((a, b) => { int c = a.dist.CompareTo(b.dist); return c != 0 ? c : a.id.CompareTo(b.id); }); m_aTabSels.Clear(); for (int i = 0; i < sorted.Count; i++) m_aTabSels.Add(sorted[i].obj); int idNewSel = 0; if (sorted.Count == 0) { m_lastAutoSelectNearestId = 0; idNewSel = idCurSel; } else { int nearestId = sorted[0].id; if (nearestId != 0 && nearestId != m_lastAutoSelectNearestId) { // If the nearest target changed (movement/spawn/despawn), reset cycle back to nearest m_lastAutoSelectNearestId = nearestId; idNewSel = nearestId; } else { m_lastAutoSelectNearestId = nearestId; int idx = -1; for (int i = 0; i < sorted.Count; i++) { if (sorted[i].id == idCurSel) { idx = i; break; } } int nextIdx = idx < 0 ? 0 : (idx + 1) % sorted.Count; idNewSel = sorted[nextIdx].id; } } // Select the target if found / 如果找到目标则选择它 if (idNewSel != 0) { if (idNewSel != idCurSel) { SelectTarget(idNewSel); } } return idNewSel; } //public float GetSwimSpeedSev() //{ // float fSpeedSev = GetSwimSpeed(); // while (true) // { // if (!IsUnderWater()) break; // //CECWorld* pWorld = g_pGame.GetGameRun().GetWorld(); // //if (!pWorld) break; // const A3DVECTOR3 vPos = GetPos(); // float fTerrainHeight = pWorld.GetTerrainHeight(vPos); // float fWaterHeight = pWorld.GetWaterHeight(vPos); // if (fWaterHeight <= fTerrainHeight) break; // float fBorderLine = fWaterHeight - 2.0f; // if (vPos.y <= fBorderLine) break; // // ·þÎñÆ÷¶Ë½«Ë®ÃæÒÔÏÂ2Ã×ÒÔÉÏ´¦ÀíΪ run_speed£¨ÓÐÎÊÌ⣩ // // µ«Î´Ê¹ÓüÓËÙ¼¼ÄÜʱ swim_speed СÓÚ run_speed£¬ // // ¿ÉÒÔÔÚË®ÃæÒÔÏÂ2Ã×ÒÔÉÏ»ñÈ¡³¬¹ý swim_speed µÄËÙ¶È£¬Òò´Ë£¬´Ë´¦È¡Á½Õß½ÏСֵΪºÏÀí×ö·¨ // fSpeedSev = min(m_ExtProps.mv.run_speed, fSpeedSev); // break; // } // return fSpeedSev; //} // Does host player have specified way point ? bool HasWayPoint(uint? wID) { if (wID == null) { return false; } for (int i = 0; i < m_aWayPoints.Count; i++) { if (m_aWayPoints[i] == wID) return true; } return false; } // Get faction role public int GetFRoleID() { return m_idFRole; } public bool InSlidingState() { if (m_iMoveMode != (int)MoveMode.MOVE_SLIDE) return false; var work = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_STAND); var standWork = work as CECHPWorkStand; if (standWork != null) { if (standWork.GetStopSlideFlag()) return false; } return true; } public FACTION_FORTRESS_CONFIG GetFactionFortressConfig() { elementdataman pDataMan = EC_Game.GetElementDataMan(); DATA_TYPE dt = DATA_TYPE.DT_INVALID; var ob = pDataMan.get_data_ptr(854, ID_SPACE.ID_SPACE_CONFIG, ref dt); FACTION_FORTRESS_CONFIG pConfig = new FACTION_FORTRESS_CONFIG(); if (ob == null || dt != DATA_TYPE.DT_FACTION_FORTRESS_CONFIG) { // ûÓлùµØÅäÖñí pConfig.require_level = int.MinValue; } else { pConfig = (FACTION_FORTRESS_CONFIG)ob; } return pConfig; } public bool IsInFortressWar() { bool bInWar = false; if (IsInFortress()) { int serverTime = EC_Game.GetServerGMTTime(); if (m_fortressEnter.end_time > serverTime) bInWar = true; } return bInWar; } public bool IsFactionMember(int idTargetFaction) { // µ±Ç°ÊôÓÚij°ïÅÉ£¬ÇÒ¶Ô·½ÊôÓÚͬһ°ïÅÉʱ·µ»Ø true return GetFactionID() != 0 && GetFactionID() == idTargetFaction; } public bool IsFactionAllianceMember(int idTargetFaction) { // µ±Ç°ÊôÓÚij°ïÅÉ£¬¶Ô·½ÊôÓÚͬһ°ïÅÉ¡¢»òÕßÊôÓÚͬÃ˰ïÅÉ return GetFactionID() != 0 && idTargetFaction != 0 && (GetFactionID() == idTargetFaction || EC_Game.GetFactionMan().IsFactionAlliance(idTargetFaction)); } public int GetCountry() { return m_idCountry; } // Get battle info. //public BATTLEINFO GetBattleInfo() { return m_BattleInfo; } //public bool IsInCountryWar() { return IsInBattle() && GetBattleInfo().IsCountryWar(); } // End NPC service public void EndNPCService() { m_idSevNPC = 0; m_bTalkWithNPC = false; m_iBoothState = 0; m_bIsInKingService = false; //m_pOffShopCtrl.SetNPCSevFlag(COfflineShopCtrl::NPCSEV_NULL); } void UpdateGFXs(float dwDeltaTime) { // if (m_pLevelUpGFX) // m_pLevelUpGFX.SetParentTM(GetAbsoluteTM()); // Update hover GFX / 更新悬停特效 if (m_pHoverGFX) // && m_idCurHover != m_idSelTarget) { if (!IsChangingFace() && (GPDataTypeHelper.ISPLAYERID(m_idCurHover) || GPDataTypeHelper.ISNPCID(m_idCurHover))) { CECObject pObject = EC_ManMessageMono.Instance?.GetObject(m_idCurHover, 1); if (pObject) { // Start GFX if stopped / 如果停止则启动特效 if (m_pHoverGFX.GetState() == GFX_STATE.ST_STOP) m_pHoverGFX.Play(); // Update parent transform every frame to follow target / 每帧更新父变换以跟随目标 if (m_pHoverGFX.transform.parent != pObject.transform) { m_pHoverGFX.transform.parent = pObject.transform; m_pHoverGFX.transform.localPosition = Vector3.zero; } } else m_pHoverGFX.Stop(true); } else m_pHoverGFX.Stop(true); } // Update selected GFX / 更新选中特效 if (m_pSelectedGFX) { if (!IsChangingFace() && (GPDataTypeHelper.ISPLAYERID(m_idSelTarget) || GPDataTypeHelper.ISNPCID(m_idSelTarget))) { CECObject pObject = EC_ManMessageMono.Instance?.GetObject(m_idSelTarget, 1); if (pObject) { // Start GFX if stopped / 如果停止则启动特效 if (m_pSelectedGFX.GetState() == GFX_STATE.ST_STOP) m_pSelectedGFX.Play(); // Update parent transform every frame to follow target / 每帧更新父变换以跟随目标 if (m_pSelectedGFX.transform.parent != pObject.transform) { m_pSelectedGFX.transform.parent = pObject.transform; m_pSelectedGFX.transform.localPosition = Vector3.zero; } } else m_pSelectedGFX.Stop(true); } else m_pSelectedGFX.Stop(true); } // if (m_pFloatDust) // { // A3DTerrainWater* pWater = g_pGame.GetGameRun().GetWorld().GetTerrainWater(); // // if (pWater.IsUnderWater(m_CameraCoord.GetPos())) // { // if (m_pFloatDust.GetState() == ST_STOP) // { // m_pFloatDust.Start(true); // m_pFloatDust.TickAnimation(2000); // } // // m_pFloatDust.SetParentTM(GetAbsoluteTM()); // } // else if (m_pFloatDust.GetState() != ST_STOP) // m_pFloatDust.Stop(); // } // UpdateMonsterSpiritGfx(dwDeltaTime); } public byte GetRealmLevel() { return m_RealmLevel; } // Level up public void LevelUp() { // CECGameSession *pSession = g_pGame.GetGameSession(); // // m_BasicProps.iLevel++; // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_LEVELUP, m_BasicProps.iLevel); // // // Get all extend properties // pSession.c2s_CmdGetExtProps(); // if (m_pLevelUpGFX) // m_pLevelUpGFX.Start(true); PlayGfx(EC_Resource.res_GFXFile((int)GfxResourceType.RES_GFX_LEVELUP), null, 1f, 1); //PLAYERMODEL_TYPEALL EventBus.Publish(new HostPlayerLevelUpUIEvent()); // // Popup notify bubble text BubbleText((int)BubbleTextType.BUBBLE_LEVELUP, 0); // // // Notify my friends that my level changed // ACHAR szInfo[40]; // a_sprintf(szInfo, _AL("L%d"), m_BasicProps.iLevel); // // for (int i=0; i < m_pFriendMan.GetGroupNum(); i++) // { // CECFriendMan::GROUP* pGroup = m_pFriendMan.GetGroupByIndex(i); // for (int j=0; j < pGroup.aFriends.GetSize(); j++) // { // CECFriendMan::FRIEND* pFriend = pGroup.aFriends[j]; // if (pFriend.IsGameOnline()) // { // pSession.SendPrivateChatData(pFriend.GetName(), // szInfo, GNET::CHANNEL_USERINFO, pFriend.id); // } // } // } // // if (GetBasicProps().iLevel==30) // { // CECGameUIMan* pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan(); // pGameUI.AddChatMessage(pGameUI.GetStringFromTable(9638), GP_CHAT_SYSTEM); // } // if (GetBasicProps().iLevel>31) // { // CECGameUIMan* pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan(); // ((CDlgOnlineAward*)pGameUI.GetDialog("Win_AddExp2")).RestartWhenLevelup(); // } } // Estimate mouse cursor private void EstimateCursor() { m_cursorUpdateTimer += Time.deltaTime; if (m_cursorUpdateTimer < CURSOR_UPDATE_INTERVAL) return; m_cursorUpdateTimer = 0f; // Early exit checks if (IsChangingFace() || mainCam == null || m_cachedMouse == null) { m_idCurHover = 0; return; } // Get mouse position using cached device Vector2 mousePosition = m_cachedMouse.position.ReadValue(); // Early exit if mouse hasn't moved significantly (2 pixel threshold) if (Vector2.Distance(mousePosition, m_lastMousePosition) < 2f) return; m_lastMousePosition = mousePosition; m_idCurHover = 0; CursorType cursorType = CursorType.RES_CUR_NORMAL; // Check modifier keys using cached keyboard bool isShiftPressed = m_cachedKeyboard != null && (m_cachedKeyboard.leftShiftKey.isPressed || m_cachedKeyboard.rightShiftKey.isPressed); Ray ray = mainCam.ScreenPointToRay(mousePosition); RaycastHit hit; // You can add a layer mask here to only raycast against specific layers // LayerMask interactableMask = LayerMask.GetMask("NPC", "Player", "Item"); // if (Physics.Raycast(ray, out hit, 100f, interactableMask)) if (Physics.Raycast(ray, out hit, 1000f)) // Reduced from 1000f to 100f for better performance { // Try to get CECObject component (cached lookup) CECObject hitObject = hit.collider.GetComponent(); if (hitObject != null) { int idHitObject = CECObject.GetObjectID(hitObject); if (idHitObject != 0) { bool bForceAttack = isShiftPressed; // Check object type and set appropriate cursor if (GPDataTypeHelper.ISNPCID(idHitObject)) { // NPC handling CECNPC pNPC = EC_ManMessageMono.Instance?.CECNPCMan?.GetNPC(idHitObject); if (pNPC != null && !pNPC.IsDead()) { m_idCurHover = idHitObject; if (m_idSelTarget == idHitObject && AttackableJudge(idHitObject, bForceAttack) == 1) { cursorType = CursorType.RES_CUR_ATTACK; } else if (pNPC.IsServerNPC()) { if (!IsInBattle() || InSameBattleCamp(pNPC)) { cursorType = CursorType.RES_CUR_TALK; } } } } else if (GPDataTypeHelper.ISPLAYERID(idHitObject)) { // Player handling EC_ElsePlayer pPlayer = EC_ManMessageMono.Instance?.GetECManPlayer?.GetPlayer(idHitObject) as EC_ElsePlayer; if (pPlayer != null) { m_idCurHover = idHitObject; if (m_idSelTarget == idHitObject && AttackableJudge(idHitObject, bForceAttack) == 1) { cursorType = CursorType.RES_CUR_ATTACK; } } } else if (GPDataTypeHelper.ISMATTERID(idHitObject)) { //todo // BMLogger.LogError($"[EstimateCursor]- GPDataTypeHelper.ISMATTERID: {idHitObject}"); // Matter/item handling (uncomment when CECMatter is implemented) // CECMatter pMatter = GetMatterManager()?.GetMatter(idHitObject); // if (pMatter != null) // { // if (!pMatter.IsMine()) // cursorType = CursorType.Pickup; // else if (CanGatherMatter(pMatter)) // cursorType = pMatter.IsMonsterSpiritMine() ? CursorType.Swallow : CursorType.Dig; // // if (cursorType != CursorType.Normal) // m_idCurHover = idHitObject; // } } } } } // Apply cursor change EC_Game.ChangeCursor((int)cursorType); } bool IsJumpInWater() { return m_bJumpInWater; } // Is under water bool IsUnderWater() { return m_iMoveEnv == Move_environment.MOVEENV_WATER ? true : false; } // Can jump or take off in water ? public bool IsSitting() { return (m_dwStates & PlayerNPCState.GP_STATE_SITDOWN) != 0 ? true : false; } // Is host player open trash box ? bool IsUsingTrashBox() { return m_bUsingTrashBox; } // Is host player talking with NPC ? public bool IsTalkingWithNPC() { return m_bTalkWithNPC; } // Is reviving bool IsReviving() { return m_pWorkMan.IsReviving(); } // Is spelling magic public bool IsSpellingMagic() { if (m_pWorkMan == null) return false; return m_pWorkMan.IsSpellingMagic(); } // Is picking up something bool IsPicking() { return false; // TODO: fix later //CECHPWork pWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_PICKUP); //if (pWork != null) //{ // return !(pWork as CECHPWorkPick).IsGather(); //} //else // return false; } // Is gathering resources public bool IsGathering() { if (m_pWorkMan == null) return false; CECHPWork pWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_PICKUP); if (pWork != null) return ((EC_HPWorkPick)pWork).IsGather(); return false; } // Is using item ? // Estimate move environment void EstimateMoveEnv(A3DVECTOR3 vPos) { if (IsFlying()) { m_iMoveEnv = Move_environment.MOVEENV_AIR; return; } CECWorld pWorld = EC_Game.GetGameRun().GetWorld(); Vector3 vStart = EC_Utility.ToVector3(vPos); Vector3 vGndPos0 = Vector3.zero; Vector3 vTestPos0 = m_MoveCtrl.GetLastSevPos() + g_vAxisY * m_aabbServer.Extents.y; VertRayTrace(vTestPos0, ref vGndPos0, ref m_GndInfo.vGndNormal, 1000f); m_GndInfo.fGndHei = vGndPos0.y; Vector3 vAABBGnd = Vector3.zero; VertAABBTrace(vTestPos0, EC_Utility.ToVector3(m_aabbServer.Extents), ref vAABBGnd, ref m_GndInfo.vGndNormal, 1000f); vAABBGnd.y -= m_aabbServer.Extents.y; bool bIsInAir = false; if (m_MoveCtrl.GetLastSevPos().y - vAABBGnd.y > 0.2f) bIsInAir = true; Vector3 vGndPos = Vector3.zero; A3DVECTOR3 vTestPos = vPos + EC_Utility.ToA3DVECTOR3(g_vAxisY) * m_aabbServer.Extents.y; VertRayTrace(EC_Utility.ToVector3(vTestPos), ref vGndPos, ref m_GndInfo.vGndNormal, 1000f); m_GndInfo.fGndHei = vGndPos.y; if (Physics.RaycastNonAlloc(EC_Utility.ToVector3(vTestPos) + Vector3.up * 500f, Vector3.down, hits, 1000f, 1 << 8) > 0) { System.Array.Sort(hits, 0, hits.Length, raycastHitDistanceComparer); m_GndInfo.fWaterHei = hits[0].point.y; } VertAABBTrace(EC_Utility.ToVector3(vTestPos), EC_Utility.ToVector3(m_aabbServer.Extents), ref vAABBGnd, ref m_GndInfo.vGndNormal, 1000f); vAABBGnd.y -= m_aabbServer.Extents.y; int iNewEnv = Move_environment.MOVEENV_GROUND; if (CheckWaterMoveEnv(vPos, m_GndInfo.fWaterHei, vAABBGnd.y)) { iNewEnv = Move_environment.MOVEENV_WATER; } if (iNewEnv == Move_environment.MOVEENV_GROUND && GetPos().y - vAABBGnd.y < 0.2f && bIsInAir && EC_Utility.ToVector3(GetPos()) != m_MoveCtrl.GetLastSevPos()) { m_MoveCtrl.SendMoveCmd(GetPos(), 2, EC_Utility.ToA3DVECTOR3(g_vAxisY), m_CDRInfo.vAbsVelocity, m_iMoveMode, true); // BubbleText(BUBBLE_LEVELUP, 0); } if (iNewEnv == Move_environment.MOVEENV_GROUND) { m_GndInfo.bOnGround = true; // if (vPos.y > m_GndInfo.fGndHei + 0.2f) if (m_CDRInfo.vTPNormal == new A3DVECTOR3(0)) { if (m_iMoveMode != (int)MoveMode.MOVE_FREEFALL) m_iMoveMode = (int)MoveMode.MOVE_FREEFALL; m_GndInfo.bOnGround = false; if (IsJumping() && m_CDRInfo.vAbsVelocity.y < 0.0f && vPos.y - vAABBGnd.y < 0.6f) { PlayAction((int)PLAYER_ACTION_TYPE.ACT_JUMP_LAND, false); } } else { if (IsJumping() && m_CDRInfo.vAbsVelocity.y < 0.0f && vPos.y - vAABBGnd.y < 0.6f) { PlayAction((int)PLAYER_ACTION_TYPE.ACT_JUMP_LAND, false); ResetJump(); } // if (m_GndInfo.vGndNormal.y < EC_SLOPE_Y) if (m_CDRInfo.vTPNormal.y < EC_SLOPE_Y) { if (!m_MoveCtrl.GetSlideLock()) { if (m_iMoveMode != (int)MoveMode.MOVE_SLIDE) { m_iOldWalkMode = m_iMoveMode; m_iMoveMode = (int)MoveMode.MOVE_SLIDE; } } else { m_MoveCtrl.SetSlideLock(false); m_iMoveMode = (int)MoveMode.MOVE_STAND; } } else { m_MoveCtrl.SetSlideLock(false); if (m_iMoveMode == (int)MoveMode.MOVE_FREEFALL) { m_iMoveMode = (int)MoveMode.MOVE_STAND; } else if (m_iMoveMode == (int)MoveMode.MOVE_SLIDE) m_iMoveMode = m_iOldWalkMode; } } } else if (iNewEnv == Move_environment.MOVEENV_WATER) { m_CDRInfo.fYVel = 0.0f; if (m_iMoveMode == (int)MoveMode.MOVE_SLIDE) { if (m_pWorkMan.IsMoving()) m_iMoveMode = (int)MoveMode.MOVE_MOVE; else m_iMoveMode = (int)MoveMode.MOVE_STAND; } } m_iMoveEnv = iNewEnv; } // ÏòÏ Trace µØÐκͽ¨Öþ£¬²¢·µ»ØµÚÒ»¸öÅöײµãµÄÇé¿ö bool VertRayTrace(Vector3 vPos, ref Vector3 vHitPos, ref A3DVECTOR3 vHitNormal, float DeltaY) { Vector3 vTerrainPos = Vector3.zero; Vector3 vTerrainNormal = Vector3.zero; Vector3 vBuildingPos = Vector3.zero; Vector3 vBuildingNormal = Vector3.zero; LayerMask layerMaskTerrain = 1 << 6; LayerMask layerMaskBush = 1 << 7; if (Physics.RaycastNonAlloc(vPos, (Vector3.down), hits, 1000f, layerMaskTerrain) > 0 /*&& hits[0].distance > 0.0009f*/) { System.Array.Sort(hits, 0, hits.Length, raycastHitDistanceComparer); vTerrainPos = hits[0].point; vTerrainNormal = hits[0].normal; } if (Physics.RaycastNonAlloc(vPos, (Vector3.down), hits, DeltaY, layerMaskBush) > 0 /*&& hits[0].distance > 0.0009f*/) { if (vBuildingPos.y > vTerrainPos.y) { // Ó뽨Öþ·¢ÉúÁËÅöײ vHitPos = vBuildingPos; vHitNormal = EC_Utility.ToA3DVECTOR3(vBuildingNormal); return true; } } if (vTerrainPos.y > vPos.y || (vTerrainPos.y <= vPos.y && vTerrainPos.y >= vPos.y - DeltaY)) { vHitPos = vTerrainPos; vHitNormal = EC_Utility.ToA3DVECTOR3(vTerrainNormal); return true; } return false; } //void VertAABBTrace(Vector3 vCenter, Vector3 vExt, ref Vector3 vHitPos, ref A3DVECTOR3 vHitNormal, // float DeltaY /* =100.0f */) //{ // vHitPos = vCenter; // vHitPos.y -= DeltaY; // LayerMask layerMask = 1 << 6 | 1 << 7; // hits = new RaycastHit[5]; // int count = Physics.BoxCastNonAlloc(vCenter, new Vector3(vExt.x, vExt.y / 10f, vExt.z), (Vector3.down).normalized, // hits, transform.rotation, DeltaY, layerMask); // if (count == 0 || (count > 0 && hits[0].distance < 0.0009f)) // { // vHitPos = vCenter; // vHitNormal = EC_Utility.ToA3DVECTOR3(Vector3.up); // } // else // { // //System.Array.Sort(hits, 0, hits.Length, raycastHitDistanceComparer); // if (Math.Abs(hits[0].distance - 0f) <= float.Epsilon) // { // // halfBox with y = 0.05f? I need Y box check too small. // count = Physics.BoxCastNonAlloc(vCenter, new Vector3(vExt.x, 0.05f, vExt.z), (Vector3.down).normalized, // hits, transform.rotation, DeltaY, layerMask); // if (count == 0 || ( count > 0 && hits[0].distance < 0.0009f)) // { // vHitPos = vCenter; // vHitNormal = EC_Utility.ToA3DVECTOR3(Vector3.up); // } // else // { // //System.Array.Sort(hits, 0, hits.Length, raycastHitDistanceComparer); // vHitPos = hits[0].point; // vHitPos.y += vExt.y; // vHitNormal = EC_Utility.ToA3DVECTOR3(hits[0].normal); // } // } // else // { // vHitPos = hits[0].point; // vHitPos.y += vExt.y; // vHitNormal = EC_Utility.ToA3DVECTOR3(hits[0].normal); // } // } //} bool VertAABBTrace(Vector3 vCenter, Vector3 vExt, ref Vector3 vHitPos, ref A3DVECTOR3 vHitNormal, float DeltaY /* =100.0f */) { ground_trace_t groundTrc = new ground_trace_t(); groundTrc.vStart = EC_Utility.ToA3DVECTOR3(vCenter); groundTrc.vExt = EC_Utility.ToA3DVECTOR3(vExt); groundTrc.fDeltaY = DeltaY; vHitPos = vCenter; vHitPos.y -= DeltaY; //RetrieveSupportPlane(&groundTrc); if (!RetrieveSupportPlane(ref groundTrc)) { vHitPos = vCenter; vHitNormal = new A3DVECTOR3(0.0f, 1.0f, 0.0f); return true; } if (groundTrc.bSupport) { vHitPos = EC_Utility.ToVector3(groundTrc.vEnd); vHitNormal = groundTrc.vHitNormal; return true; } return false; } // Get cool time public virtual int GetCoolTime(int iIndex, out int piMax /* NULL */) { piMax = 1; if (iIndex >= 0 && iIndex < (int)CoolTimeIndex.GP_CT_MAX) { if (piMax > 0) piMax = m_aCoolTimes[iIndex].iMaxTime; return m_aCoolTimes[iIndex].iCurTime; } return 0; } // Play a pose public bool CmdStartPose(int iPose) { // first of all see if we need to cancel sitdown work. if (m_pWorkMan.IsSitting()) { UnityGameSession.c2s_CmdStandUp(); return false; } if (!CanDo(ActionCanDo.CANDO_PLAYPOSE)) return false; if (!m_pWorkMan.IsStanding()) return false; if (iPose == (int)RoleExpression.ROLEEXP_SITDOWN) { // UnityGameSession.c2s_CmdSessionEmote(iPose); } else if (iPose == (int)RoleExpression.ROLEEXP_KISS) { // if (GPDataTypeHelper.ISPLAYERID(m_idSelTarget)) // UnityGameSession.c2s_CmdConEmoteRequest(RoleExpression.ROLEEXP_KISS, m_idSelTarget); } else UnityGameSession.c2s_CmdEmoteAction((uint)iPose); return true; } public enum StateAnim { Idle = 1, Walk = 2, Run = 3, Jump = 4 } // Mask of some special extend states which will influence host game logic. // Logic Influence Extned states [Flags] public enum Logic_Influence_Extned_states { LIES_SLEEP = 0x0001, LIES_STUN = 0x0002, LIES_ROOT = 0x0004, LIES_NOFGIHT = 0x0008, LIES_DISABLEFIGHT = 0x000B, } public enum BubbleTextType { BUBBLE_DAMAGE = 0, BUBBLE_EXP, BUBBLE_SP, BUBBLE_MONEY, BUBBLE_LEVELUP, BUBBLE_HITMISSED, BUBBLE_INVALIDHIT, BUBBLE_IMMUNE, BUBBLE_HPWARN, BUBBLE_MPWARN, BUBBLE_REBOUND, // ·´µ¯ BUBBLE_BEAT_BACK, // ·´»÷ BUBBLE_ADD, // ÎüѪµÄ¼ÓºÅ BUBBLE_DODGE_DEBUFF, BUBBLE_REALMEXP, } // ½øÈë»ùµØÐÅÏ¢ public struct FACTION_FORTRESS_ENTER { public int faction_id; public int role_in_war; // 0 : ÖÐÁ¢»ò²»ÔÚ»ùµØ 1:¹¥·½ 2: ÊØ·½ public int end_time; public FACTION_FORTRESS_ENTER(int faction_id, int role_in_war, int end_time) { this.faction_id = faction_id; this.role_in_war = role_in_war; this.end_time = end_time; } } public struct EXPToUpLevel { public int NeededExp; public EXPToUpLevel(int neededExp) { NeededExp = neededExp; } } public enum MOVE_DIR { MD_FORWARD = 0x01, MD_RIGHT = 0x02, MD_BACK = 0x04, MD_LEFT = 0x08, MD_ABSUP = 0x10, MD_ABSDOWN = 0x20, MD_ALL = 0x3f, }; public struct NPCINFO { public string Name; // Movement properties public int CurrentHealth; public int MaxHealth; // Attacking properties public int IDNPC; // Attacking properties public NPCINFO(string name, int currentHealth, int maxHealth, int idnpc) { Name = name; CurrentHealth = currentHealth; MaxHealth = maxHealth; IDNPC = idnpc; } } /// Fired when selected target HUD should be hidden (deselect or switch). public struct TargetHUDClearEvent { } /// Host leveled up — UI layer may refresh quest-offer lists, popups, etc. 玩家升级,UI 可刷新可接任务等。 public struct HostPlayerLevelUpUIEvent { } public struct GNDINFO { public float fGndHei; // Ground height public float fWaterHei; // Water height public A3DVECTOR3 vGndNormal; // Terrain normal public bool bOnGround; // On ground flag }; public struct InfoHostPlayer { public string NameHostPlayer; public InfoHostPlayer(string name) { NameHostPlayer = name; } } public struct REINCARNATION_TOME { public int tome_exp; public char tome_active; // 1¼¤»î0δ¼¤»î public int max_level; // ÀúÊ·×î¸ßµÈ¼¶ cmd_reincarnation_tome_info._entry[] reincarnations; static int max_exp; }; public struct cmd_reincarnation_tome_info { public int tome_exp; public char tome_active; // 1¼¤»î0δ¼¤»î public int count; public struct _entry { int level; int timestamp; int exp; } public _entry[] records; }; // Behavior id used by CanDo() public static class ActionCanDo { public const int CANDO_SITDOWN = 0, CANDO_MOVETO = 1, CANDO_MELEE = 2, CANDO_ASSISTSEL = 3, CANDO_FLY = 4, CANDO_PICKUP = 5, CANDO_TRADE = 6, CANDO_PLAYPOSE = 7, CANDO_SPELLMAGIC = 8, CANDO_USEITEM = 9, CANDO_JUMP = 10, CANDO_FOLLOW = 11, CANDO_GATHER = 12, CANDO_BOOTH = 13, CANDO_FLASHMOVE = 14, CANDO_BINDBUDDY = 15, CANDO_DUEL = 16, CANDO_SUMMONPET = 17, CANDO_CHANGESELECT = 18, CANDO_REBUILDPET = 19, CANDO_SWITCH_PARALLEL_WORLD = 20; } public bool FindMineTool(int tidMine, ref int piPack, ref int piIndex, ref int pidTool) { if (tidMine == 0) return false; DATA_TYPE DataType = DATA_TYPE.DT_INVALID; object pDataPtr = ElementDataManProvider.GetElementDataMan() .get_data_ptr((uint)tidMine, ID_SPACE.ID_SPACE_ESSENCE, ref DataType); if (DataType != DATA_TYPE.DT_MINE_ESSENCE) { //ASSERT(DataType != DATA_TYPE.DT_MINE_ESSENCE); return false; } MINE_ESSENCE pData = (MINE_ESSENCE)pDataPtr; int idTool = (int)pData.id_equipment_required; bool bRet = true; if (idTool != 0) { int iIndex = IvtrPack.FindItem(idTool); if (iIndex >= 0) { piPack = EC_Inventory.Inventory_type.IVTRTYPE_PACK; piIndex = iIndex; pidTool = idTool; } else if ((iIndex = IvtrTaskPack.FindItem(idTool)) >= 0) { piPack = EC_Inventory.Inventory_type.IVTRTYPE_TASKPACK; piIndex = iIndex; pidTool = idTool; } else bRet = false; } else { piPack = 0; piIndex = 0; pidTool = 0; } return bRet; } public CECCounter GetGatherCnt() { return m_GatherCnt; } public void UpdateTimers(float dwDeltaTime) { // Get real time tick of this frame var iRealTime = EC_Game.GetRealTickTime(); // Update flysword time /*if (IsFlying() && GetRushFlyFlag()) { CECIvtrItem pItem = m_pEquipPack.GetItem(EQUIPIVTR_FLYSWORD); Debug.Assert(pItem != null); if (pItem.GetClassID() == CECIvtrItem.ICID_FLYSWORD) { CECIvtrFlySword pFlySword = (CECIvtrFlySword)pItem; pFlySword.TimePass(dwDeltaTime); } }*/ int i; // Update skills for (i = 0; i < m_aPtSkills.Count; i++) m_aPtSkills[i].Tick(dwDeltaTime); for (i = 0; i < m_aPsSkills.Count; i++) m_aPsSkills[i].Tick(dwDeltaTime); for (i = 0; i < m_aGoblinSkills.Count; i++) m_aGoblinSkills[i].Tick(dwDeltaTime); for (i = 0; i < m_aEquipSkills.Count; i++) m_aEquipSkills[i].Tick(dwDeltaTime); if (m_pTargetItemSkill != null) m_pTargetItemSkill.Tick(dwDeltaTime); //CECComboSkillState.Instance().Tick(); // Update cool times for (i = 0; i < (int)CoolTimeIndex.GP_CT_MAX; i++) m_aCoolTimes[i].Update(dwDeltaTime); foreach (var kvp in m_skillCoolTime) { kvp.Value.Update(dwDeltaTime); } // Gather time counter if (m_GatherCnt.IncCounter(iRealTime)) m_GatherCnt.Reset(true); // Incant time counter if (m_IncantCnt.IncCounter(dwDeltaTime * 1000)) m_IncantCnt.Reset(true); m_PetOptCnt.IncCounter(iRealTime); // Bind command cool counter /* if (m_BindCmdCoolCnt.IncCounter(dwDeltaTime)) m_BindCmdCoolCnt.Reset(true); // Auto fashion time counter if (m_bAutoFashion && GetBoothState() != 2 && !IsShapeChanged()) { if (m_AutoFashionCnt.IncCounter(dwDeltaTime)) { if (!CheckAutoFashionCondition()) { while (!EquipFashionBySuitID((m_iCurFashionSuitID + 1) % GetMaxFashionSuitNum())) m_iCurFashionSuitID++; m_AutoFashionCnt.Reset(); } } } // Auto convert Yinpiao if (m_AutoYinpiao.open) { if (m_AutoYinpiao.cnt.IncCounter(dwDeltaTime)) { ExchangeYinpiao(); m_AutoYinpiao.cnt.Reset(); } } // Control the dialog of the target item m_TargetItemDlgCtrl.Update(dwDeltaTime); // For some reasons on server, sometimes friend list couldn't be got // successfully. Try to get it again every 20s if this case really happen if (m_pFriendMan != null && !m_pFriendMan.CheckInit()) { m_iGetFriendCnt -= dwDeltaTime; if (m_iGetFriendCnt < 0) { EC_Game.GetGameSession().friend_GetList(); m_iGetFriendCnt = 60000; } } // Duel stopping time counter if (m_pvp.iDuelState == DUEL_ST_PREPARE) { m_pvp.iDuelTimeCnt -= dwDeltaTime; if (m_pvp.iDuelTimeCnt < 0) m_pvp.iDuelTimeCnt = 0; } else if (m_pvp.iDuelState == DUEL_ST_INDUEL) { m_pvp.iDuelTimeCnt += dwDeltaTime; } else if (m_pvp.iDuelState == DUEL_ST_STOPPING) { m_pvp.iDuelTimeCnt -= dwDeltaTime; if (m_pvp.iDuelTimeCnt < 0) { m_pvp.iDuelTimeCnt = 0; m_pvp.iDuelState = DUEL_ST_NONE; m_pvp.idDuelOpp = 0; } } // Update pariah time counter if (m_dwPariahTime > 0) { if (m_dwPariahTime > (uint)dwDeltaTime) m_dwPariahTime -= (uint)dwDeltaTime; else m_dwPariahTime = 0; } // Update pet operation time counter m_PetOptCnt.IncCounter(dwDeltaTime); // Update battle result time counter if (IsInBattle() && !IsInFortress() && m_BattleInfo.iResult != 0 && m_BattleInfo.iResultCnt != 0) { // iResultCnt is time counter (likely in milliseconds as int), dwDeltaTime is in seconds (float) // iResultCnt是时间计数器(可能是毫秒为int),dwDeltaTime是秒数(float) // Convert seconds to milliseconds and subtract / 将秒转换为毫秒并减去 int deltaTimeMs = (int)(dwDeltaTime * 1000f); if ((m_BattleInfo.iResultCnt -= deltaTimeMs) < 0) m_BattleInfo.iResultCnt = 0; } // Update pet corral if (m_pPetCorral != null) m_pPetCorral.Tick((uint)dwDeltaTime); // Update the related people var keysToRemove = new List(); foreach (var kvp in m_RelatedPlayer) { m_RelatedPlayer[kvp.Key] -= dwDeltaTime; if (m_RelatedPlayer[kvp.Key] <= 0) keysToRemove.Add(kvp.Key); } foreach (var key in keysToRemove) { m_RelatedPlayer.Remove(key); }*/ } public struct COOLTIME { public int iCurTime; public int iMaxTime; public void Update(float dwDeltaTime) { if (iCurTime > 0) { iCurTime -= (int)(dwDeltaTime * 1000); Mathf.Clamp(iCurTime, 0, float.MaxValue); } } } // Start / Stop flying public bool CmdFly(bool bForceFly) { //if (m_pActionSwitcher) // m_pActionSwitcher.PostMessge(CECActionSwitcherBase::MSG_FLY); // first of all see if we need to cancel sitdown work. if (!CanDo(ActionCanDo.CANDO_FLY)) return false; EC_IvtrItem pItem = IvtrEquipPack.GetItem(InventoryConst.EQUIPIVTR_FLYSWORD); if (pItem == null) return false; if (pItem is EC_IvtrEquip) { if ((pItem as EC_IvtrEquip).IsDestroying()) return false; } if (!IsFlying()) { // TODO: Maybe we should let server tell us whether we can fly or not bool bCanFly = true; if (m_iMoveEnv == (int)MoveEnvironment.MOVEENV_AIR) bCanFly = false; else if (m_iMoveEnv == (int)MoveEnvironment.MOVEENV_WATER && !CanTakeOffWater()) bCanFly = false; if (!bCanFly) return false; } UnityGameSession.c2s_SendCmdUseItem(InventoryConst.IVTRTYPE_EQUIPPACK, InventoryConst.EQUIPIVTR_FLYSWORD, pItem.GetTemplateID(), 1); return true; } public CECCounter GetIncantCnt() { return m_IncantCnt; } // Get key object(NPC..) coordinates public A3DVECTOR3 GetObjectCoordinates(int idTarget, out List TargetCoord, ref bool bInTable) { TargetCoord = new List(); A3DVECTOR3 vDestPos = new A3DVECTOR3(0, 0, 0); bInTable = false; // Get object coordinates from CECGame::m_CoordTab string szText = idTarget.ToString(); List originalCoords = new List(); int iCount = EC_Game.GetObjectCoord(szText, out originalCoords); if (originalCoords == null || iCount == 0) { return vDestPos; } float fMinDist = 99999999.0f; // Get Current map name, such as 'a32' etc. CECGameRun gameRun = CECGameRun.Instance; if (gameRun == null) return vDestPos; CECWorld world = gameRun.GetWorld(); if (world == null) return vDestPos; int idInstance = world.GetInstanceID(); CECInstance pInstance = gameRun.GetInstance(idInstance); if (pInstance == null) return vDestPos; string strCurMap = pInstance.GetPath() ?? string.Empty; // �ȼ��ͬһ��ͼ���Ƿ���Ҫ���ҵ���Ʒ bool bHasObjectInCurrentInstance = originalCoords.Any(coord => coord.strMap == strCurMap); // Iterate over original list and build filtered TargetCoord list for (int i = 0; i < iCount; i++) { OBJECT_COORD objCoord = originalCoords[i]; if (strCurMap == objCoord.strMap) { TargetCoord.Add(objCoord); // Check if this is the nearest target float tempDist = (objCoord.vPos - GetPos()).Magnitude(); if (tempDist < fMinDist) { fMinDist = tempDist; bInTable = true; vDestPos = objCoord.vPos; } } // ��ǰ��ͼ��û��Ŀ��Ļ�����Ŀ�����ڵ�ͼ�ڵ�ǰ��ͼ����� else if (!bHasObjectInCurrentInstance) { if (string.IsNullOrEmpty(objCoord.strMap)) continue; // find the entrance of instance List instCoord = new List(); int iCount2 = EC_Game.GetObjectCoord(objCoord.strMap, out instCoord); for (int j = 0; j < iCount2; ++j) { if (instCoord[j].strMap == strCurMap) { TargetCoord.Add(instCoord[j]); // Check if this is the nearest target float tempDist = (instCoord[j].vPos - GetPos()).Magnitude(); if (tempDist < fMinDist) { fMinDist = tempDist; bInTable = true; vDestPos = instCoord[j].vPos; } } } } } return vDestPos; } public int GetRealmLayer() { return m_RealmLevel / 100; } public int GetRealmSubLevel() { return m_RealmLevel % 100; } public static int GetRealmLayer(int realmLevel) { return realmLevel > 0 ? (realmLevel + 9) / 10 : 0; } public static int GetRealmSubLevel(int realmLevel) { return realmLevel > 0 ? (realmLevel % 10 > 0 ? realmLevel % 10 : 10) : 0; } // // Calculate distance to an object and optionally retrieve the object reference // 计算到对象的距离,并可选地获取对象引用 // // Target object ID / 目标对象ID // Output distance / 输出距离 // Output object reference (optional) / 输出对象引用(可选) // True if calculation succeeded / 计算成功返回true public bool CalcDist(int idObject, out float pfDist, out CECObject ppObject) { pfDist = 0.0f; ppObject = null; if (idObject == 0 || idObject == m_PlayerInfo.cid) return false; CECWorld pWorld = CECGameRun.Instance.GetWorld(); if (pWorld == null) return false; CECObject pObject = pWorld.GetObject(idObject, 1); if (ppObject == null) return false; ppObject = pObject; float fDist = 0.0f; if (GPDataTypeHelper.ISNPCID(idObject)) { CECNPC pNPC = pObject as CECNPC; if (pNPC == null) return false; fDist = pNPC.CalcDist(GetPos(), true); } else if (GPDataTypeHelper.ISMATTERID(idObject)) { Debug.Assert(pObject.GetClassID() == Class_ID.OCID_MATTER); CECMatter pMatter = pObject as CECMatter; if (pMatter == null) return false; A3DVECTOR3 vDelta = pMatter.GetPos() - GetPos(); fDist = A3d_Magnitude(vDelta); } else { return false; } pfDist = fDist; return true; } // Helper method overload without object output public bool CalcDist(int idObject, out float pfDist) { CECObject pObject; return CalcDist(idObject, out pfDist, out pObject); } private void UpdatePosWing() { if(m_HH_chibang == null) { m_HH_chibang = FindChildRecursive(m_pPlayerModel.transform, "HH_chibang"); } if(m_HH_feijian == null) { m_HH_feijian = FindChildRecursive(m_pPlayerModel.transform, "HH_feijian"); } if(m_Wing != null) { if (m_wingType == enumWingType.WINGTYPE_WING) { if(m_CC_chibang == null) { m_CC_chibang = FindChildRecursive(m_Wing, "CC_chibang"); } m_delta_CC_to_HH = (m_Wing.position - m_CC_chibang.position); m_Wing.position = m_HH_chibang.position + m_delta_CC_to_HH; } else if (m_wingType == enumWingType.WINGTYPE_FLYSWORD) { if (m_CC_feijian == null) { m_CC_feijian = FindChildRecursive(m_Wing, "CC_feijian"); } //m_delta_CC_to_HH = (m_Wing.position - m_CC_feijian.position); //m_Wing.position = m_HH_feijian.position + m_delta_CC_to_HH; m_Wing.position = m_HH_feijian.position; } //else //{ //} } //if (m_Wing2 != null) //{ // if (m_wingType == enumWingType.WINGTYPE_WING) // { // m_Wing.transform.position = m_HH_chibang.transform.position; // } // else if (m_wingType == enumWingType.WINGTYPE_FLYSWORD) // { // m_Wing.transform.position = m_HH_feijian.transform.position; // } // else // { // } //} } //// ID checking helper methods //private bool ISNPCID(int id) => ((id & 0x80000000) != 0) && ((id & 0x40000000) == 0); //private bool ISPLAYERID(int id) => id != 0 && (id & 0x80000000) == 0; //private bool ISMATTERID(int id) => ((id) & 0xC0000000) == 0xC0000000; // Release object public void Release() { // TODO: Release all objects created by player, such as inventory, skills, etc. // CECInstanceReenter::Instance().Clear(); // CECShoppingItemsMover::Instance().Clear(); // CECFashionShopManager::Instance().Clear(); // CECShoppingManager::Instance().Clear(); // CECUseUniversalTokenCommandManager::Instance().Clear(); // CECUniversalTokenHTTPOSNavigatorTicketHandler::Instance().Clear(); // RandMallShoppingManager::Instance().Release(); // CECFactionPVPModel::Instance().Clear(); // CECHostSkillModel::Instance().Release(); // CECComboSkillState::Instance().Release(); // CECPlayerLevelRankRealmChangeCheck::Instance().Release(); // CECHostFashionEquipFromStorageSystem::Instance().Clear(); // // m_pSaveLifeTrigger = NULL; // CECQuickBuyPopManager::Instance().ClearPolicies(); // // // Ïú»ÙPlayerWrapper // CECAutoPolicy::GetInstance().OnLeaveWorld(); // // // Save favorite auction list first // SaveFavorAucItems(); // // // Release duel images // ReleaseDuelImages(); // // // Release sounds // g_pGame->GetGameRun()->ReleaseSoundTable(); // m_pCurMoveSnd = NULL; // // // Release friend manger // if (m_pFriendMan) // { // delete m_pFriendMan; // m_pFriendMan = NULL; // } // // // Release pet corral // if (m_pPetCorral) // { // delete m_pPetCorral; // m_pPetCorral = NULL; // } // // if (m_pPetWords) // { // delete m_pPetWords; // m_pPetWords = NULL; // } // // if (m_pForceMgr) // { // delete m_pForceMgr; // m_pForceMgr = NULL; // } // // if (m_pOnlineAwardCtrl) // { // delete m_pOnlineAwardCtrl; // m_pOnlineAwardCtrl = NULL; // } // // if (m_pOffShopCtrl) // { // delete m_pOffShopCtrl; // m_pOffShopCtrl = NULL; // } // // if (m_pAutoTeam) // { // delete m_pAutoTeam; // m_pAutoTeam = NULL; // } // // if (m_pChariot) // { // delete m_pChariot; // m_pChariot = NULL; // } // // int i; // // // Release all shortcuts // for (i=0; i < NUM_HOSTSCSETS1; i++) // A3DRELEASE(m_aSCSets1[i]); // // for (i=0; i < NUM_HOSTSCSETS2; i++) // A3DRELEASE(m_aSCSets2[i]); // // for (i=0; i < NUM_SYSMODSETS; i++) // A3DRELEASE(m_aSCSetSysMod[i]); // // // Release all inventories // A3DRELEASE(m_pPack); // A3DRELEASE(m_pEquipPack); // A3DRELEASE(m_pTrashBoxPack); // A3DRELEASE(m_pTrashBoxPack2); // A3DRELEASE(m_pTrashBoxPack3); // A3DRELEASE(m_pAccountBoxPack); // A3DRELEASE(m_pGeneralCardPack); // A3DRELEASE(m_pTaskPack); // A3DRELEASE(m_pDealPack); // A3DRELEASE(m_pEPDealPack); // A3DRELEASE(m_pTaskInterface); // A3DRELEASE(m_pSpritePortrait); // A3DRELEASE(m_pBuyPack); // A3DRELEASE(m_pSellPack); // A3DRELEASE(m_pBoothSPack); // A3DRELEASE(m_pBoothBPack); // A3DRELEASE(m_pEPBoothSPack); // A3DRELEASE(m_pEPBoothBPack); // A3DRELEASE(m_pEPEquipPack); // A3DRELEASE(m_pClientGenCardPack); // // for (i=0; i < NUM_NPCIVTR; i++) // { // A3DRELEASE(m_aNPCPacks[i]); // } // // // Release all skills // ReleaseSkills(); // // // Clear current combo skill // ClearComboSkill(); // // if (m_pWorkMan) // { // delete m_pWorkMan; // m_pWorkMan = NULL; // } // // m_CameraCtrl.Release(); // // m_aTeamInvs.RemoveAll(); // // g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pMoveTargetGFX); // g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pSelectedGFX); // g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pHoverGFX); // g_pGame->GetGFXCaster()->ReleaseGFXEx(m_pFloatDust); // // m_pMoveTargetGFX = NULL; // m_pSelectedGFX = NULL; // m_pHoverGFX = NULL; // m_pFloatDust = NULL; // // // Clear tab select table // m_aTabSels.RemoveAll(false); // // m_aForceInfo.RemoveAll(); // // if (m_pActionSwitcher) // { // delete m_pActionSwitcher; // m_pActionSwitcher = NULL; // } // // CECQShopConfig::Instance().ClearBuyedItem(); // // A3DRELEASE(m_pNavigatePlayer); // // CECPlayer::Release(); } public int GetCurServiceNPC() { return m_idSevNPC; } // ──────────────────────────────────────────────────────────────────── // Move sound — C++ equivalent: CECHostPlayer::UpdateMoveSound / PlayMoveSound // ──────────────────────────────────────────────────────────────────── /// /// Selects and plays the correct looping movement SFX based on environment, /// shape, and movement state. Throttled to run once every 3 frames, matching /// the C++ dwUpdateCnt % 3 guard. /// private void UpdateMoveSound() { _moveSndUpdateCounter++; if (_moveSndUpdateCounter % 3 != 0) return; if (m_pWorkMan == null) return; int newId; if (m_pWorkMan.IsMoving()) { if (m_iMoveEnv == (int)MoveEnvironment.MOVEENV_GROUND) { if (IsJumping()) { newId = 162; } else if (!m_GndInfo.bOnGround) { newId = 163; } else { int iIndex = m_iProfession * GENDER.NUM_GENDER + m_iGender; int iWalkRunOffset = m_bWalkRun ? 1 : 0; if (IsShapeChanged() && (GetShapeID() == (int)ModelResourceType.RES_MOD_ORC_FOX || GetShapeID() == (int)ModelResourceType.RES_MOD_ORC_FOX2)) newId = 170 + iWalkRunOffset; else if (IsShapeChanged() && GetShapeID() == (int)ModelResourceType.RES_MOD_ORC_TIGER) newId = 172 + iWalkRunOffset; else if (m_RidingPet.id != 0) newId = 200 + GetRidingPetSndType(m_RidingPet.id) * 2 + iWalkRunOffset; else newId = (m_bWalkRun ? 130 : 100) + iIndex; } } else if (m_iMoveEnv == (int)MoveEnvironment.MOVEENV_WATER) { float eyeY = GetPosVector3().y + 1.7f; // approximate eye height newId = eyeY > m_GndInfo.fWaterHei ? 160 : 161; } else // MOVEENV_AIR { newId = 164; } } else { newId = 164; } PlayMoveSound(newId); } /// /// Switches the looping move sound to , no-op if already playing. /// C++ equivalent: CECHostPlayer::PlayMoveSound /// private void PlayMoveSound(int id) { if (id == _curMoveSndId) return; _curMoveSndId = id; if(id > 0) { SFXManager.Instance?.PlayMoveSoundAsync(id).Forget(); } /*else { SFXManager.Instance?.StopMoveSoundAsync().Forget(); }*/ } /// /// Returns the pet_snd_type for a riding pet, used to select the mount SFX series /// (200 + type*2 + walkRunOffset). /// TODO: look up PET_ESSENCE.pet_snd_type via ElementDataManager when available. /// C++ equivalent: pData->pet_snd_type from PET_ESSENCE /// private static int GetRidingPetSndType(int petId) { return 0; // default type 0 = horse (walk 200, run 201) } } public sealed class CECHPTraceSpellMatcher : CECHPWorkMatcher { public override bool Match(CECHPWork pWork, int priority, bool isDelayWork) { if (pWork == null) return false; if (pWork.GetWorkID() != Host_work_ID.WORK_TRACEOBJECT) return false; // dynamic_cast(pWork) trong C# if (pWork is not CECHPWorkTrace trace) return false; return trace.GetTraceReason() == Trace_reason.TRACE_SPELL; } } public struct RaycastHitDistanceComparer : System.Collections.Generic.IComparer { public int Compare(RaycastHit x, RaycastHit y) { return x.distance.CompareTo(y.distance); } } }