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.Skills; using BrewMonster.Scripts.World; using CSNetwork; using CSNetwork.GPDataType; using CSNetwork.Protocols.RPCData; using ModelRenderer.Scripts.GameData; using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using BrewMonster.PerfectWorld.Scripts.Vfx; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using Trace_reason = BrewMonster.CECHPWorkTrace.Trace_reason; using Host_work_ID = BrewMonster.Scripts.CECHPWork.Host_work_ID; using PerfectWorld.Scripts.Managers; 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 // Shortcut sets (converted from C++: m_aSCSets1[NUM_HOSTSCSETS1], m_aSCSets2[NUM_HOSTSCSETS2]) public CECShortcutSet[] m_aSCSets1 = new CECShortcutSet[HostCfgConstants.NUM_HOSTSCSETS1]; // SC set 1 public CECShortcutSet[] m_aSCSets2 = new CECShortcutSet[HostCfgConstants.NUM_HOSTSCSETS2]; // SC set 2 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 List m_aTabSels = new List(); private List m_aPtSkills = new List(); private List m_aPsSkills = new List(); private List m_aEquipSkills = new List(); private List m_aGoblinSkills = new List(); 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 bool m_bSpellDSkill; private CECSkill m_pCurSkill; private CECSkill m_pTargetItemSkill; // Target item skill public CECComboSkill m_pComboSkill; // Combo skill public A3DVECTOR3 m_vAccel; // Accelerate 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 A3DVECTOR3 g_vOrigin = new A3DVECTOR3(0f); private Transform playerTransform; 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; // 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; // Player auto team / automatic team grouping // ====== 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 hit; private BaseVfxObject m_pSelectedGFX; private BaseVfxObject m_pHoverGFX; // 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; 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_packInventory = new EC_Inventory(); private readonly EC_Inventory m_equipInventory = new EC_Inventory(); private readonly EC_Inventory m_taskInventory = new EC_Inventory(); public EC_Inventory PackInventory => m_packInventory; public EC_Inventory EquipInventory => m_equipInventory; public EC_Inventory TaskInventory => m_taskInventory; private void OnApplicationQuit() { if (m_pTaskInterface != null) m_pTaskInterface.Despose(); } public bool IsMeleeing() { return m_bMelee; } public CECAutoTeam GetAutoTeam() { return m_pAutoTeam; } public EC_Inventory GetTaskPack() { return m_taskInventory; } public EC_Inventory GetInventory(byte byPackage) { switch (byPackage) { case InventoryConst.IVTRTYPE_PACK: return m_packInventory; case InventoryConst.IVTRTYPE_EQUIPPACK: return m_equipInventory; case InventoryConst.IVTRTYPE_TASKPACK: return m_taskInventory; default: return null; } } private static class HostCfgConstants { public const int HOSTCFG_VERSION = 11; public const int NUM_HOSTSCSETS1 = 5; // expanded from 3 to 5 (2009.05.27) public const int NUM_HOSTSCSETS2 = 3; public const int SIZE_HOSTSCSET1 = 9; // expanded from 6 to 9 (2009.05.27) public const int SIZE_HOSTSCSET2 = 8; } 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; } m_TaskCounter.SetCounter(3000f); m_bTitleDataReady = false; playerTransform = transform; } public bool 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; if (!LoadPlayerSkeleton(true)) { BMLogger.LogError("HoangDev CECHostPlayer::LoadResources, Failed to load skeleton"); return false; } return true; } private bool LoadPlayerSkeleton(bool bAtOnce) { EC_PLAYERLOADRESULT Ret = default; if (bAtOnce /*|| !IsLoadThreadReady()*/) { // Under normal circumstances, only HostPlayer can reach here /* if (!LoadPlayerModel(m_iProfession, m_iGender, m_CustomizeData.bodyID, aEquips, szPetPath, Ret, false, false)) { a_LogOutput(1, "CECPlayer::Init, failed to call LoadPlayerModel() !"); return false; }*/ SetPlayerLoadedResult(Ret); /* if (IsShapeChanged() && !QueueLoadDummyModel(m_iShape, true)) { // ignore the dummy model loading failure a_LogOutput(1, "CECPlayer::Init, failed to call QueueLoadDummyModel() !"); }*/ } return true; } private bool SetPlayerLoadedResult(EC_PLAYERLOADRESULT ret) { OnAllResourceReady(); return true; } private void OnAllResourceReady() { CECHostSkillModel.Instance.Initialize(); } 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 UNITY_EDITOR if (Input.GetKeyDown(KeyCode.C)) { ApplySkillShortcut(1); } #endif // 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(); // track status of Task TickTask(); m_pWorkMan?.Tick(Time.deltaTime); // Update GFXs UpdateGFXs(Time.deltaTime); //m_dwMoveRelDir = 0; m_fVertSpeed = 0.0f; // Auto team / Automatic party grouping // m_pAutoTeam.Tick(Time.deltaTime); } public void StopMovement() { m_MoveCtrl.SendStopMoveCmd(playerTransform.position, 5f, (int)GPMoveMode.GP_MOVE_WALK); } public void HandleMovement() { // 1) Kiểm tra grounded bằng SphereCast ngắn dựa trên radius + skinWidth isGrounded = GroundCheck(out lastGroundHit); m_GndInfo.bOnGround = isGrounded; // 2) Input tạm thời: giữ nguyên như bạn //if (UnityEngine.Input.GetKeyDown(KeyCode.LeftShift)) SetStatusRun(true); //if (UnityEngine.Input.GetKeyUp(KeyCode.LeftShift)) SetStatusRun(false); //if (UnityEngine.Input.GetKeyDown(KeyCode.Space)) HandleJump(); // 3) Trọng lực / sticky if (isGrounded && playerVelocity.y < 0f) { // Đè nhẹ để bám đất (tránh nhấp-nháy) playerVelocity.y = -2f; } else { playerVelocity.y += gravityValue * Time.deltaTime; } // 4) Chuyển động phẳng float x = joystick.Horizontal; float z = joystick.Vertical; Vector3 move = new Vector3(x, 0, z); move = Vector3.ClampMagnitude(move, 1f); if (move != Vector3.zero) { Vector3 finalMove = (move * playerSpeed) + (playerVelocity.y * Vector3.up); controller.Move(finalMove * Time.deltaTime); transform.forward = move; m_MoveCtrl.GroundMove(Time.deltaTime); m_MoveCtrl.SendMoveCmd(playerTransform.position, controller.velocity, (int)GPMoveMode.GP_MOVE_RUN); m_aabb.Center = EC_Utility.ToA3DVECTOR3(playerTransform.position) + new A3DVECTOR3(0.0f, m_aabb.Extents.y, 0.0f); m_aabb.CompleteMinsMaxs(); m_aabbServer.Center = EC_Utility.ToA3DVECTOR3(playerTransform.position) + new A3DVECTOR3(0.0f, m_aabbServer.Extents.y, 0.0f); m_aabbServer.CompleteMinsMaxs(); } else { } } private void JoystickRelease(JoystickRealeaseEvent joystickRealeaseEvent) { // _playerStateMachine.ChangeState(_idleState); } 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 int value when value == EC_MsgDef.MSG_HST_CORRECTPOS: OnMsgHstCorrectPos(Msg); break; case int value when value == EC_MsgDef.MSG_HST_GOTO: OnMsgHstGoto(Msg); break; case int value when value == EC_MsgDef.MSG_HST_IVTRINFO: { OnMsgHstIvtrInfo(Msg); break; } case int value when value == EC_MsgDef.MSG_HST_OWNITEMINFO: { OnMsgHstOwnItemInfo(Msg); break; } case int value when value == EC_MsgDef.MSG_HST_TASKDATA: { OnMsgHstTaskData(Msg).Forget(); //Debug.LogError("[Dat]- OnMsgHstTaskData"); break; } case int value when value == EC_MsgDef.MSG_HST_ITEMOPERATION: OnMsgHstItemOperation(Msg); break; case int value when value == EC_MsgDef.MSG_HST_PICKUPITEM: OnMsgHstPickupItem(Msg); break; case int value when value == EC_MsgDef.MSG_HST_SELTARGET: OnMsgHstSelTarget(Msg); break; case int value when value == EC_MsgDef.MSG_HST_ATKRESULT: OnMsgHstAttackResult(Msg); break; case int value when value == EC_MsgDef.MSG_HST_ATTACKED: OnMsgHstAttacked(Msg); break; case int value when value == EC_MsgDef.MSG_HST_HURTRESULT: OnMsgHstHurtResult(Msg); break; case int value when value == EC_MsgDef.MSG_HST_INFO00: OnMsgHstInfo00(Msg); break; case int value when value == EC_MsgDef.MSG_HST_NPCGREETING: OnMsgHstNPCGreeting(Msg); break; case int value when value == EC_MsgDef.MSG_HST_WAYPOINT: OnMsgHstWayPoint(Msg); break; case int value when value == EC_MsgDef.MSG_HST_SKILLDATA: OnMsgHstSkillData(Msg); break; case int value when value == EC_MsgDef.MSG_HST_DIED: OnMsgHstDied(Msg); break; case int value when value == EC_MsgDef.MSG_HST_STARTATTACK: OnMsgHstStartAttack(Msg); break; case int value when value == EC_MsgDef.MSG_HST_STOPATTACK: OnMsgHstStopAttack(Msg); break; case int value when value == EC_MsgDef.MSG_HST_SKILLRESULT: OnMsgHstSkillResult(Msg); break; case int value when value == EC_MsgDef.MSG_PM_CASTSKILL: OnMsgPlayerCastSkill(Msg); break; case int value when value == EC_MsgDef.MSG_PM_ENCHANTRESULT: OnMsgEnchantResult(Msg); break; case int value when value == EC_MsgDef.MSG_HST_OWNEXTPROP: OnMsgHstExtProp(Msg); break; case int value when value == EC_MsgDef.MSG_PM_PLAYERDOEMOTE: OnMsgPlayerDoEmote(Msg); break; case int value when value == EC_MsgDef.MSG_HST_TARGETISFAR: OnMsgHstTargetIsFar(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 OnMsgEnchantResult(ECMSG msg) { // 从消息中获取cmd_enchant_result结构 // Get cmd_enchant_result structure from message cmd_enchant_result pCmd = GPDataTypeHelper.FromBytes((byte[])msg.dwParam1); // 初始化掩码为全1 // Initialize mask to all 1s uint mask = 0xFFFFFFFF; // 如果目标不是主机玩家且当前不是主机玩家,则过滤会引起气泡文本的修饰符 // We should filter out these things that will cause bubble texts CECHostPlayer pHost = EC_ManMessageMono.Instance.GetECManPlayer.GetHostPlayer(); if (pCmd.target != pHost.GetCharacterID() && !IsHostPlayer()) { mask &= (uint)(MOD.MOD_PHYSIC_ATTACK_RUNE | MOD.MOD_MAGIC_ATTACK_RUNE | MOD.MOD_CRITICAL_STRIKE | MOD.MOD_ENCHANT_FAILED); } // 获取修饰符 // Get modifier uint dwModifier = (uint)pCmd.attack_flag; // 获取技能类型 // Get skill type int nDamage = -2; // 默认为-2,不会引起受伤动作 // Default to -2, will not cause wounded action if (ElementSkill.GetType((uint)pCmd.skill) == (byte)skill_type.TYPE_ATTACK) { // 只有攻击技能会引起受伤动作,伤害值为-1 // Only attack skill will cause wounded action, when damage is -1 nDamage = -1; } else { // 其他技能不会引起受伤动作,伤害值设为-2 // Other skills will not cause wounded action, so we set damage to -2 nDamage = -2; } // 播放攻击效果 // Play attack effect int attackTime = 0; PlayAttackEffect(pCmd.target, pCmd.skill, pCmd.level, nDamage, dwModifier & mask, 0, ref attackTime, pCmd.section); } private void OnMsgPlayerCastSkill(ECMSG Msg) { bool bDoOtherThing = false; int idTarget = 0; bool bActionStartSkill = false; int iActionTime = 1000; // CECPlayerWrapper* pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper(); switch (Convert.ToInt32(Msg.dwParam2)) { case int value2 when value2 == CommandID.OBJECT_CAST_SKILL: { // Cast skill on target object // 对目标对象施法 cmd_object_cast_skill pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); BMLogger.LogError("pCmd.caster == m_PlayerInfo.cid =" + (pCmd.caster == m_PlayerInfo.cid)); if (m_pCurSkill != null) { m_pCurSkill.EndCharging(); } m_pCurSkill = GetPositiveSkillByID(pCmd.skill); if (m_pCurSkill == null) m_pCurSkill = GetEquipSkillByID(pCmd.skill); if (m_pCurSkill == null) m_pCurSkill = CECComboSkillState.Instance.GetInherentSkillByID((uint)pCmd.skill); if (m_pCurSkill == null) { Debug.Assert(m_pCurSkill != null, "Current skill should not be null"); return; } if (m_pCurSkill.IsChargeable()) m_pCurSkill.StartCharging(pCmd.time); int iWaitTime = -1; if (m_pCurSkill.GetExecuteTime() >= 0) iWaitTime = pCmd.time + m_pCurSkill.GetExecuteTime(); CECHPWorkSpell pWork = (CECHPWorkSpell)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_SPELLOBJECT); pWork.PrepareCast(pCmd.target, m_pCurSkill, iWaitTime); m_pWorkMan.StartWork_p1(pWork); // Start time counter for some type skill // 为某些类型的技能启动时间计数器 if (!m_pCurSkill.IsChargeable()) { int iTime = pCmd.time; if (iTime < 10) iTime = 10; // a_ClampFloor m_IncantCnt.SetPeriod(iTime); m_IncantCnt.Reset(); } else { // make sure the counter is correct shown // 确保计数器正确显示 m_IncantCnt.Reset(true); } m_bSpellDSkill = false; TurnFaceTo(pCmd.target); m_idCurSkillTarget = pCmd.target; PlaySkillCastAction(m_pCurSkill.GetSkillID()); bActionStartSkill = true; iActionTime = iWaitTime; Debug.Log($"Cast skill({m_pCurSkill.GetSkillID()})"); break; } case int value2 when value2 == CommandID.SKILL_PERFORM: { // Skill perform // 技能执行 m_pPrepSkill = null; if (m_pCurSkill != null && m_pCurSkill.IsDurative()) m_bSpellDSkill = true; Debug.Log($"SKILL_PERFORM)"); break; } case int value2 when value2 == CommandID.HOST_STOP_SKILL: { // Host stop skill // 主角停止技能 m_pPrepSkill = null; CECSkill pSkillToMatch = m_pCurSkill; // 保存指针值,用在后面函数调用中 | Save pointer value for later function call if (m_pCurSkill != null) { ClearComActFlagAllRankNodes(true); if (((m_pComboSkill != null && !m_pComboSkill.IsStop()) || m_pCurSkill.ChangeToMelee()) && !m_pWorkMan.HasDelayedWork()) { bDoOtherThing = true; idTarget = m_idCurSkillTarget; } m_pCurSkill.EndCharging(); m_pCurSkill = null; } Debug.Log("HOST_STOP_SKILL"); //AP_ActionEvent(AP_EVENT_STOPSKILL); if (pSkillToMatch != null) { // m_pWorkMan中的当前任何Work为最高优先级或、或 CECHPWorkSpell 处于最高优先级且队列中有Delay时间 // 此时此处无法准确匹配,而导致的 CECHPWorkSpell 执行时将落后于其它 CECHPWorkSpell 执行期间将无法响应 // 在这种方法执行完成后,我们使用回城机制来 // If the current Work in m_pWorkMan is the highest priority or CECHPWorkSpell is at the highest priority and there is Delay time in the queue // At this point, it cannot be accurately matched, causing the CECHPWorkSpell execution to lag behind other CECHPWorkSpell execution and cannot respond // After this method is executed, we use the return to city mechanism m_pWorkMan.FinishWork(new CECHPWorkSpellMatcher(pSkillToMatch)); } StopSkillAttackAction(); m_idCurSkillTarget = 0; break; } case int value2 when value2 == CommandID.SELF_SKILL_INTERRUPTED: { // Skill interrupted // 技能被打断 int skill_id = 0; m_pPrepSkill = null; CECSkill pSkillToMatch = m_pCurSkill; // 保存指针值,用在后面函数调用中 | Save pointer value for later function call if (m_pCurSkill != null) { skill_id = m_pCurSkill.GetSkillID(); ClearComActFlagAllRankNodes(false); if (((m_pComboSkill != null && !m_pComboSkill.IsStop()) || m_pCurSkill.ChangeToMelee()) && !m_pWorkMan.HasDelayedWork()) { bDoOtherThing = true; idTarget = m_idCurSkillTarget; } m_pCurSkill.EndCharging(); m_pCurSkill = null; } m_idCurSkillTarget = 0; if (pSkillToMatch != null) { // 逻辑同 HOST_STOP_SKILL 分支处理 | Logic same as HOST_STOP_SKILL branch m_pWorkMan.FinishWork(new CECHPWorkSpellMatcher(pSkillToMatch)); } StopSkillCastAction(); // Print a notify message // 打印提示消息 // g_pGame->GetGameRun()->AddFixedMessage(FIXMSG_SKILLINTERRUPT); Debug.Log("Skill interrupted!"); //AP_ActionEvent(AP_EVENT_STOPSKILL); // 通知策略技能被打断 | Notify policy that skill is interrupted // CECAutoPolicy::GetInstance().SendEvent_SkillInterrupt(skill_id); break; } case int value2 when value2 == CommandID.OBJECT_CAST_INSTANT_SKILL: { // Cast instant skill // 施放即时技能 cmd_object_cast_instant_skill pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); Debug.Assert(pCmd.caster == m_PlayerInfo.cid); CECSkill pSkill = GetPositiveSkillByID(pCmd.skill); if (pSkill == null) pSkill = GetEquipSkillByID(pCmd.skill); if (pSkill == null) { Debug.Assert(pSkill != null, "Skill should not be null"); return; } if (pCmd.target != 0 && pCmd.target != m_PlayerInfo.cid) TurnFaceTo(pCmd.target); PlaySkillCastAction(pSkill.GetSkillID()); bActionStartSkill = true; break; } case int value2 when value2 == CommandID.OBJECT_CAST_POS_SKILL: { // Cast position skill (target position instead of target object) // 施放位置技能(目标位置而不是目标对象) cmd_object_cast_pos_skill pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); Debug.Assert(pCmd.caster == m_PlayerInfo.cid); CECSkill pSkill = GetNormalSkill(pCmd.skill); if (pSkill == null) pSkill = GetEquipSkillByID(pCmd.skill); if (pSkill == null) { Debug.Assert(pSkill != null, "Skill should not be null"); break; } TurnFaceTo(pCmd.target); if (pSkill.GetRangeType() != (int)CECSkill.RangeType.RANGE_SLEF && pSkill.GetRangeType() != (int)CECSkill.RangeType.RANGE_SELFSPHERE && (pSkill.GetSkillID() == 1095 || // 瞬影瞬斩 | Shadow instant slash pSkill.GetSkillID() == 1278 || // 魔·瞬影瞬斩 | Demon Shadow instant slash pSkill.GetSkillID() == 1279 || // 妖瞬影瞬斩 | Monster Shadow instant slash pSkill.GetSkillID() == 2313)) { A3DVECTOR3 vPos = pCmd.pos; if (!IsPosCollideFree(vPos)) { // Add a little height to ensure player's AABB won't embed with building // 添加一点高度以确保玩家的AABB不会嵌入建筑物 vPos += new A3DVECTOR3(0, 1, 0) * 0.1f; } // Ensure we are not under ground // 确保我们不在地下 A3DVECTOR3 vNormal = new A3DVECTOR3(); float vTerrainHeight = CECWorld.Instance.GetTerrainHeight(vPos, ref vNormal); if (vPos.y < vTerrainHeight) vPos.y = vTerrainHeight; SetPos(EC_Utility.ToVector3(vPos)); m_CDRInfo.vTPNormal = vPos.y <= vTerrainHeight + 0.1f ? vNormal : new A3DVECTOR3(0); m_CDRInfo.fYVel = 0.0f; m_CDRInfo.vAbsVelocity = new A3DVECTOR3(0); ResetJump(); m_MoveCtrl.SetHostLastPos(vPos); m_MoveCtrl.SetLastSevPos(vPos); // Update camera // 更新相机 //UpdateFollowCamera(false, 10); } else { CECHPWorkFMove pWork = (CECHPWorkFMove)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_FLASHMOVE); // 检查技能执行时间,防止出现浮点数0,出现错误 | Check skill execute time to prevent float 0 error int nExecuteTime = pSkill.GetExecuteTime(); if (nExecuteTime < 50) nExecuteTime = 50; // a_ClampFloor if (pSkill.GetSkillID() == 1145 || // 刺客的瞬移技能,需要增加地面效果1145:百步穿杨瞬斩 | Assassin teleport skill pSkill.GetSkillID() == 1314 || // 魔·百步穿杨瞬斩 | Demon hundred steps instant slash pSkill.GetSkillID() == 1315 || // 妖·百步穿杨瞬斩 | Monster hundred steps instant slash pSkill.GetSkillID() == 1362 || // 影遁回杨 | Shadow escape return pSkill.GetSkillID() == 1690 || // 影遁魔·回杨 | Shadow escape demon return pSkill.GetSkillID() == 1691 || // 影遁妖回杨 | Shadow escape monster return pSkill.GetSkillID() == 1845 || // 星湖之狐·瞬移技能 | Star Lake Fox teleport pSkill.GetSkillID() == 1844 || // 星湖之狐·瞬移技能 | Star Lake Fox teleport pSkill.GetSkillID() == 1815 || // 妖精 飞 | Fairy fly pSkill.GetSkillID() == 2272 || // 一步登天 | One step to heaven pSkill.GetSkillID() == 2315 || // 百步穿杨瞬斩 | Hundred steps instant slash pSkill.GetSkillID() == 2285 || // 回杨 | Return pSkill.GetSkillID() == 2340 || // 突袭 | Raid pSkill.GetSkillID() == 2341 || // 突袭 | Raid pSkill.GetSkillID() == 2342 || // 突袭 | Raid pSkill.GetSkillID() == 2553 || // 飞箭雨 | Arrow rain pSkill.GetSkillID() == 2740 || // 魔·飞箭雨 | Demon arrow rain pSkill.GetSkillID() == 2741 || // 妖飞箭雨 | Monster arrow rain pSkill.GetSkillID() == 2559 || // 闪电 | Lightning pSkill.GetSkillID() == 2752 || // 魔·闪电 | Demon lightning pSkill.GetSkillID() == 2753) // 妖闪电 | Monster lightning { A3DVECTOR3 vPos = pCmd.pos; if (!IsPosCollideFree(vPos)) { // Add a little height to ensure player's AABB won't embed with building // 添加一点高度以确保玩家的AABB不会嵌入建筑物 vPos += new A3DVECTOR3(0, 1, 0) * 0.1f; } // Ensure we are not under ground // 确保我们不在地下 A3DVECTOR3 vNormal = new A3DVECTOR3(); float vTerrainHeight = CECWorld.Instance.GetTerrainHeight(vPos, ref vNormal); if (vPos.y < vTerrainHeight) vPos.y = vTerrainHeight; m_CDRInfo.vTPNormal = vPos.y <= vTerrainHeight + 0.1f ? vNormal : new A3DVECTOR3(0); m_CDRInfo.fYVel = 0.0f; m_CDRInfo.vAbsVelocity = new A3DVECTOR3(0); ResetJump(); pWork.PrepareMove(pCmd.pos, nExecuteTime * 0.001f, pSkill.GetSkillID()); } else { if (pSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SLEF || pSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SELFSPHERE) { m_CDRInfo.vTPNormal = m_MoveCtrl.m_vFlashTPNormal; } pWork.PrepareMove(pCmd.pos, nExecuteTime * 0.001f, 0); } m_pWorkMan.StartWork_p2(pWork); iActionTime = nExecuteTime; } bActionStartSkill = true; break; } case int value2 when value2 == CommandID.PLAYER_CAST_RUNE_SKILL: { // Cast rune skill (item skill) // 施放符文技能(物品技能) cmd_player_cast_rune_skill pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); Debug.Assert(pCmd.caster == m_PlayerInfo.cid); if (m_pTargetItemSkill != null) { // Delete old target item skill m_pTargetItemSkill = null; } m_pTargetItemSkill = new CECSkill(pCmd.skill, pCmd.level); if (m_pTargetItemSkill == null) { Debug.Assert(m_pTargetItemSkill != null, "Target item skill should not be null"); return; } m_pCurSkill = m_pTargetItemSkill; if (m_pCurSkill.IsChargeable()) m_pCurSkill.StartCharging(pCmd.time); int iWaitTime = -1; if (m_pCurSkill.GetExecuteTime() >= 0) iWaitTime = pCmd.time + m_pCurSkill.GetExecuteTime(); CECHPWorkSpell pWork = (CECHPWorkSpell)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_SPELLOBJECT); pWork.PrepareCast(pCmd.target, m_pCurSkill, iWaitTime); m_pWorkMan.StartWork_p1(pWork); // Start time counter for some type skill // 为某些类型的技能启动时间计数器 if (!m_pCurSkill.IsChargeable()) { int iTime = pCmd.time; if (iTime < 10) iTime = 10; // a_ClampFloor m_IncantCnt.SetPeriod(iTime); m_IncantCnt.Reset(); } else { // make sure the counter is correct shown // 确保计数器正确显示 m_IncantCnt.Reset(true); } m_bSpellDSkill = false; TurnFaceTo(pCmd.target); m_idCurSkillTarget = pCmd.target; PlaySkillCastAction(m_pCurSkill.GetSkillID()); bActionStartSkill = true; iActionTime = iWaitTime; Debug.Log($"Cast rune skill({m_pCurSkill.GetSkillID()})"); break; } case int value2 when value2 == CommandID.PLAYER_CAST_RUNE_INSTANT_SKILL: { // Cast instant rune skill // 施放即时符文技能 cmd_player_cast_rune_instant_skill pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); Debug.Assert(pCmd.caster == m_PlayerInfo.cid); if (m_pTargetItemSkill != null) { // Delete old target item skill m_pTargetItemSkill = null; } m_pTargetItemSkill = new CECSkill(pCmd.skill, pCmd.level); if (m_pTargetItemSkill == null) { Debug.Assert(m_pTargetItemSkill != null, "Target item skill should not be null"); return; } if (pCmd.target != 0 && pCmd.target != m_PlayerInfo.cid) TurnFaceTo(pCmd.target); PlaySkillCastAction(m_pTargetItemSkill.GetSkillID()); bActionStartSkill = true; break; } case int value2 when value2 == CommandID.ERROR_MESSAGE: bDoOtherThing = true; break; default: Debug.Assert(false, "Unknown message type in OnMsgPlayerCastSkill"); break; } /* if (bActionStartSkill) AP_ActionEvent(AP_EVENT_STARTSKILL, iActionTime);*/ if (bDoOtherThing) { if (m_pComboSkill != null && !m_pComboSkill.IsStop()) { // Continue combo skill // 继续连击技能 // 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 OnMsgHstSkillResult(ECMSG Msg) { cmd_host_skill_attack_result pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); int refFake = 0; PlayAttackEffect(pCmd.idTarget, pCmd.idSkill, 0, pCmd.iDamage, (uint)pCmd.attack_flag, pCmd.attack_speed * 50, ref refFake, pCmd.section); } private void OnMsgHstSkillData(ECMSG Msg) { cmd_skill_data pCmd = default; pCmd.skill_count = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); int offset = sizeof(uint); int skillSize = Marshal.SizeOf(); pCmd.skill_list = new cmd_skill_data.SKILL[pCmd.skill_count]; for (int i = 0; i < pCmd.skill_count; i++) { pCmd.skill_list[i] = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1, offset); offset += skillSize; } if (pCmd.skill_list == null) { BMLogger.LogError("OnMsgHstSkillData: cmd is null"); return; } /* List skillSCConfigArray1 = new List(); List skillSCConfigArray2 = new List(); List skillGrpSCConfigArray1 = new List(); List skillGrpSCConfigArray2 = new List();*/ if (HostIsReady()) { /* for (int i = 0; i < HostConstants.NUM_HOSTSCSETS1; i++) { if (hostPlayer.m_aSCSets1[i] != null) { hostPlayer.m_aSCSets1[i].RemoveSkillShortcuts(); } } for (int i = 0; i < HostConstants.NUM_HOSTSCSETS2; i++) { if (hostPlayer.m_aSCSets2[i] != null) { hostPlayer.m_aSCSets2[i].RemoveSkillShortcuts(); } }*/ // Release passive skills m_aPsSkills.Clear(); } // Load skill data from command // C++: GNET::ElementSkill::LoadSkillData(pCmd); ElementSkill.LoadSkillData(pCmd); // Create skill objects from command data for (int i = 0; i < pCmd.skill_count; i++) { cmd_skill_data.SKILL data = pCmd.skill_list[i]; CECSkill skill = new CECSkill(data.id_skill, data.level); // Categorize skills into positive and passive int skillType = skill.GetType(); if (skillType != (int)CECSkill.SkillType.TYPE_PASSIVE && skillType != (int)CECSkill.SkillType.TYPE_PRODUCE && skillType != (int)CECSkill.SkillType.TYPE_LIVE) { m_aPtSkills.Add(skill); } else { m_aPsSkills.Add(skill); } } // Restore and convert shortcuts after loading new skills /* if (hostPlayer.HostIsReady()) { hostPlayer.ConvertSkillShortcut(skillSCConfigArray1); hostPlayer.AssignSkillShortcut(skillSCConfigArray1, hostPlayer.m_aSCSets1); hostPlayer.ConvertSkillShortcut(skillSCConfigArray2); hostPlayer.AssignSkillShortcut(skillSCConfigArray2, hostPlayer.m_aSCSets2); hostPlayer.ConvertComboSkill(); hostPlayer.ValidateSkillGrpShortcut(skillGrpSCConfigArray1); hostPlayer.AssignSkillGrpShortcut(skillGrpSCConfigArray1, hostPlayer.m_aSCSets1); hostPlayer.ValidateSkillGrpShortcut(skillGrpSCConfigArray2); hostPlayer.AssignSkillGrpShortcut(skillGrpSCConfigArray2, hostPlayer.m_aSCSets2); } if (hostPlayer.HostIsReady()) { // Update UI when profession changes, save all shortcut configurations // to remove effects from intermediate skills (invalid pointers) // C++: CECGameUIMan *pGameUIMan = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan(); // pGameUIMan.UpdateSkillRelatedUI(); hostPlayer.UpdateSkillRelatedUI(); }*/ } public bool HostIsReady() { return true /*m_bEnterGame*/; } private void OnMsgHstDied(in ECMSG msg) { // Mark host player as corpse so CECPlayer.IsDead() returns true m_dwStates |= (uint)PlayerNPCState.GP_STATE_CORPSE; EventBus.PublishChannel(GetCharacterID(), new ClearComActFlagAllRankNodesEvent(true)); PlayAction((int)PLAYER_ACTION_TYPE.ACT_GROUNDDIE); if (PopupManager.Instance != null) { PopupManager.Instance.OnPlayerDied(); } } private void OnMsgHstInfo00(in ECMSG Msg) { cmd_self_info_00 pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); bool bFirstTime = m_BasicProps.iLevel == 0 ? true : false; if (!bFirstTime) { int iLimit = (int)(pCmd.iMaxHP * 0.3f); if (pCmd.iHP < m_BasicProps.iCurHP && m_BasicProps.iCurHP >= iLimit && pCmd.iHP < iLimit) { /*if (CECUIHelper::GetGameUIMan().IsShowLowHP()) { // ѪÁ¿µÍÓÚÁÙ½çÖµÔò²¥·ÅÌØÐ§ const int GfxLastTime = 10000; // ³ÖÐøÊ±¼ä10Ãë CECUIHelper::GetGameUIMan().GetScreenEffectMan().StartEffect(CECScreenEffect::EFFECT_REDSPARK, GfxLastTime); }*/ } /*if (pCmd.iHP >= iLimit || pCmd.iHP <= 0) { // ѪÁ¿¸ßÓÚÁÙ½çÖµ»òËÀÍö£¬ÔòÍ£Ö¹²¥·ÅÌØÐ§ CECUIHelper::GetGameUIMan().GetScreenEffectMan().FinishEffect(CECScreenEffect::EFFECT_REDSPARK); }*/ /*iLimit = (int)(pCmd.iMaxMP * 0.2f); if (pCmd.iMP < m_BasicProps.iCurMP && m_BasicProps.iCurMP >= iLimit && pCmd.iMP < iLimit) BubbleText(BUBBLE_MPWARN, 0);*/ /*if (m_ExtProps.max_ap != pCmd.iMaxAP) g_pGame.GetGameRun().AddFixedMessage(FIXMSG_ADDMAXAP, pCmd.iMaxAP - m_ExtProps.max_ap);*/ } m_BasicProps.iLevel = pCmd.sLevel; SetLevel2(pCmd.Level2, bFirstTime); m_BasicProps.iExp = pCmd.iExp; m_BasicProps.iSP = pCmd.iSP; m_BasicProps.iCurHP = pCmd.iHP; m_BasicProps.iCurMP = pCmd.iMP; m_BasicProps.iCurAP = pCmd.iAP; m_ExtProps.bs.max_hp = pCmd.iMaxHP; m_ExtProps.bs.max_mp = pCmd.iMaxMP; m_ExtProps.max_ap = pCmd.iMaxAP; EventBus.Publish(new EXPToUpLevel(GetLevelUpExp(pCmd.sLevel))); EventBus.Publish(pCmd); EventBus.PublishChannel(GetCharacterID(), pCmd); // if (pCmd.State != 0 && m_bFight == false) PlayEnterBattleGfx(); m_bFight = pCmd.State != 0 ? true : false; // UpdateGodEvilSprite(); /*CECGameUIMan* pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan(); CDlgAutoHelp *pDlgHelp = dynamic_cast(pGameUI.GetDialog("Win_WikiPop"));*/ /*if(pDlgHelp && m_bFight) pDlgHelp.SetAutoHelpState(false);*/ } void SetLevel2(int level2, bool bFirstTime) { int lastLevel2 = m_BasicProps.iLevel2; m_BasicProps.iLevel2 = level2; /*if (CanPlayTaoistEffect(lastLevel2, level2, bFirstTime)){ PlayTaoistEffect(); }*/ } void OnMsgHstAttacked(ECMSG Msg) { var m_pPlayerMan = EC_ManMessageMono.Instance.EC_ManPlayer; cmd_host_attacked pCmd = GPDataTypeHelper.FromBytes(Msg.dwParam1 as byte[]); if (pCmd.iDamage != 0 && (pCmd.cEquipment & 0x7f) != 0x7f) { /* char cEquip = (char)(pCmd.cEquipment & 0x7f); CECIvtrEquip pEquip = (CECIvtrEquip)m_pEquipPack.GetItem(cEquip); if (pEquip) pEquip.AddCurEndurance(ARMOR_RUIN_SPEED);*/ } // The host player is attacked, we should make an effect here if (GPDataTypeHelper.ISPLAYERID(pCmd.idAttacker)) { EC_ElsePlayer pAttacker = m_pPlayerMan.GetElsePlayer(pCmd.idAttacker); if (pAttacker) { if (!pAttacker.IsDead()) { // Face to target pAttacker.TurnFaceTo(GetPlayerInfo().cid); } int useless_attacktime = 0; pAttacker.PlayAttackEffect(GetCharacterID(), 0, 0, pCmd.iDamage, (uint)pCmd.attack_flag, pCmd.speed * 50, ref useless_attacktime); pAttacker.EnterFightState(); } } else if (GPDataTypeHelper.ISNPCID(pCmd.idAttacker)) { CECNPC pAttacker = EC_ManMessageMono.Instance.CECNPCMan.GetNPC(pCmd.idAttacker); if (pAttacker) { pAttacker.OnMsgAttackHostResult(GetCharacterID(), pCmd.iDamage, pCmd.attack_flag, pCmd.speed); } } //CECAutoPolicy::GetInstance().SendEvent_BeHurt(pCmd.idAttacker); } public void OnMsgHstAttackResult(ECMSG Msg) { byte[] data = Msg.dwParam1 as byte[]; cmd_host_attack_result pCmd = EC_Utility.ByteArrayToStructure(data); int iAttackTime = 0; TurnFaceTo(pCmd.idTarget); PlayAttackEffect(pCmd.idTarget, 0, 0, pCmd.iDamage, (uint)pCmd.attack_flag, pCmd.attack_speed * 50, ref iAttackTime); if (iAttackTime != 0) { if (m_pWorkMan.GetRunningWork(CECHPWork.Host_work_ID.WORK_HACKOBJECT) is CECHPWorkMelee pCurWork) { pCurWork.SetIdleTime(iAttackTime); } } } /* public override bool IsWorkMoveRunning() { return m_pWorkMan.IsMovingToPosition(); }*/ private void OnMsgHstHurtResult(ECMSG Msg) { //BMLogger.LogError("HoangDev : OnMsgHstHurtResult"); /* int cmd = Convert.ToInt32(Msg.dwParam2); if (cmd == CommandID.BE_HURT) { cmd_be_hurt pCmd = (cmd_be_hurt)Msg.dwParam1; if (pCmd.damage != 0) Damaged(pCmd.damage); } else if (cmd == CommandID.HURT_RESULT) { cmd_hurt_result pCmd = (cmd_hurt_result)Msg.dwParam1; if (pCmd.target_id == m_PlayerInfo.cid) return; // Host himself will receive BE_HURT, so ignore this. if (UnityGameSession.Instance.GameSession.ISPLAYERID(pCmd.target_id)) { CECElsePlayer pTarget = m_pPlayerMan.GetElsePlayer(pCmd.target_id); if (pTarget) pTarget.Damaged(pCmd.damage); } else if (UnityGameSession.Instance.GameSession.ISNPCID(pCmd.target_id)) { CECNPC pTarget = EC_ManMessageMono.Instance._CECNPCMan.GetNPC(pCmd.target_id); if (pTarget) pTarget.Damaged(pCmd.damage); } }*/ } public bool IsPosCollideFree(A3DVECTOR3 vTargetPos) { bool bAvailable = (false); while (true) { A3DVECTOR3 refake = new A3DVECTOR3(); float terrianHeight = CECWorld.Instance.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 = CECWorld.Instance; 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 = CECWorld.Instance; 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 OnMsgHstPickupItem(in ECMSG Msg) { var data = Msg.dwParam1 as byte[]; int cmd = Convert.ToInt32(Msg.dwParam2); bool bDoOther = false; int idItem, iExpireDate = 0, iAmount, iCmdLastSlot, iCmdSlotAmount, iPack, iMsg = -1; switch (cmd) { case CommandID.HOST_OBTAIN_ITEM: { // Parse cmd_host_obtain_item struct data int type = BitConverter.ToInt32(data, 0); int expire_date = BitConverter.ToInt32(data, 4); uint amount = BitConverter.ToUInt32(data, 8); uint slot_amount = BitConverter.ToUInt32(data, 12); byte where = data[16]; // Package index byte index = data[17]; // Slot index in that package // Create new inventory item data var newItem = new EC_IvtrItem { Package = where, Slot = index, m_tid = type, m_expire_date = expire_date, State = 0, m_iCount = (int)amount, Crc = 0, Content = null }; // Add item to inventory var ivt = GetInventory(where); ivt.SetItem(index, newItem); Debug.Log( $"[HOST_OBTAIN_ITEM] Successfully added item {type} to package {where}, slot {index} with count {amount}"); // Trigger UI refresh if an EC_InventoryUI is present in scene var ui = GameObject.FindFirstObjectByType(); if (ui != null) { ui.RefreshAll(); } UpdateEquipSkins(); } break; case CommandID.PICKUP_ITEM: { int tid = BitConverter.ToInt32(data, 0); int expire_date = BitConverter.ToInt32(data, 4); iAmount = (int)BitConverter.ToUInt32(data, 8); uint iSlotAmount = BitConverter.ToUInt32(data, 12); byte byPackage = data[16]; byte bySlot = data[17]; //Debug.Log($"[Inventory] PICKUP_ITEM: tid={tid}, expire_date={expire_date}, iAmount={iAmount}, iSlotAmount={iSlotAmount}, byPackage={byPackage}, bySlot={bySlot}"); // Notify pickupItem script about successful pickup pickupItem pickupScript = pickupItem.Instance; if (pickupScript != null) { //Debug.Log($"[Inventory] PICKUP_ITEM: tid={tid}, expire_date={expire_date}, iAmount={iAmount}, iSlotAmount={iSlotAmount}, byPackage={byPackage}, bySlot={bySlot}"); // Notify pickupItem script about successful pickup pickupScript = UnityEngine.Object.FindFirstObjectByType(); if (pickupScript != null) { pickupScript.OnPickupSuccess(tid); } // Create new inventory item data var newItem = new EC_IvtrItem { Package = byPackage, Slot = bySlot, m_tid = tid, m_expire_date = expire_date, State = 0, m_iCount = (int)iAmount, Crc = 0, Content = null }; // Add item to inventory var ivt = GetInventory(byPackage); ivt.SetItem(bySlot, newItem); //Debug.Log($"[Inventory] Successfully added item {tid} to package {byPackage}, slot {bySlot} with count {iAmount}"); // Trigger UI refresh if an EC_InventoryUI is present in scene var ui = GameObject.FindFirstObjectByType(); if (ui != null) { ui.RefreshAll(); } } else { Debug.LogWarning("[Inventory] PICKUP_ITEM: Invalid data length"); } break; } case CommandID.TASK_DELIVER_ITEM: cmd_task_deliver_item pCmd = GPDataTypeHelper.FromBytes(data); // ASSERT(pCmd); idItem = pCmd.type; iExpireDate = pCmd.expire_date; iAmount = (int)pCmd.amount; iCmdLastSlot = pCmd.index; iCmdSlotAmount = (int)pCmd.slot_amount; iPack = pCmd.where; iMsg = (int)FixedMsg.FIXMSG_GAINITEM; bDoOther = true; // Create new inventory item data var taskNewItem = new EC_IvtrItem { Package = (byte)iPack, Slot = iCmdLastSlot, m_tid = idItem, m_expire_date = iExpireDate, State = 0, m_iCount = (int)iAmount, Crc = 0, Content = null }; // Add item to inventory var task_ivt = GetInventory((byte)iPack); task_ivt.SetItem(iCmdLastSlot, taskNewItem); //Debug.Log($"[Inventory] Successfully added item {tid} to package {byPackage}, slot {bySlot} with count {iAmount}"); // Trigger UI refresh if an EC_InventoryUI is present in scene var task_ui = GameObject.FindFirstObjectByType(); if (task_ui != null) { task_ui.RefreshAll(); } break; // TODO: Handle other pickup item commands if necessary } } public void OnMsgHstItemOperation(ECMSG Msg) { var data = Msg.dwParam1 as byte[]; int cmd = Convert.ToInt32(Msg.dwParam2); switch (cmd) { case CommandID.PLAYER_DROP_ITEM: { // Parse the drop item data from the server response if (data != null && data.Length >= 6) { byte byPackage = data[0]; byte bySlot = data[1]; int count = BitConverter.ToInt32(data, 2); int tid = BitConverter.ToInt32(data, 6); byte reason = data[10]; Debug.Log( $"[Inventory] PLAYER_DROP_ITEM: package={byPackage}, slot={bySlot}, count={count}, tid={tid}, reason={reason}"); // Update the inventory by removing the item var inv = GetInventory(byPackage); bool success = inv != null && inv.RemoveItem(bySlot, count); if (success) { Debug.Log( $"[Inventory] Successfully removed {count} items from package {byPackage}, slot {bySlot}"); // Trigger UI refresh if an EC_InventoryUI is present in scene var ui = GameObject.FindFirstObjectByType(); if (ui != null) { ui.RefreshAll(); } } else { Debug.LogWarning( $"[Inventory] Failed to remove items from package {byPackage}, slot {bySlot}"); } } else { Debug.LogWarning("[Inventory] PLAYER_DROP_ITEM: Invalid data length"); } break; } case CommandID.EQUIP_ITEM: { byte index_inv = data[0]; byte index_equip = data[1]; // Update client-side data: move item between PACK_INVENTORY and PACK_EQUIPMENT var packInv = GetInventory(InventoryConst.IVTRTYPE_PACK); var equipInv = GetInventory(InventoryConst.IVTRTYPE_EQUIPPACK); var invItem = packInv?.GetItem(index_inv, true); var equipItem = equipInv?.GetItem(index_equip, true); UpdateEquipSkins(); if (invItem != null) { invItem.Package = InventoryConst.IVTRTYPE_EQUIPPACK; invItem.Slot = index_equip; equipInv?.SetItem(index_equip, invItem); } if (equipItem != null) { equipItem.Package = InventoryConst.IVTRTYPE_PACK; equipItem.Slot = index_inv; packInv?.SetItem(index_inv, equipItem); } // Trigger UI refresh if an EC_InventoryUI is present in scene var ui = GameObject.FindObjectOfType(); if (ui != null) { ui.RefreshAll(); } UpdateEquipSkins(); break; } } } public void OnMsgHstOwnItemInfo(ECMSG Msg) { int cmd = Convert.ToInt32(Msg.dwParam2); switch (cmd) { case CommandID.OWN_ITEM_INFO: { Debug.Log("[Inventory] OWN_ITEM_INFO received"); var data = Msg.dwParam1 as byte[]; int hostId = Convert.ToInt32(Msg.dwParam3); LogInventoryPacket("OWN_ITEM_INFO", data, hostId); break; } } } public void OnMsgHstIvtrInfo(ECMSG Msg) { var data = Msg.dwParam1 as byte[]; int cmd = Convert.ToInt32(Msg.dwParam2); int hostId = Convert.ToInt32(Msg.dwParam3); switch (cmd) { case CommandID.OWN_IVTR_DATA: { LogInventoryPacket("OWN_IVTR_DATA", data, hostId); break; } case CommandID.OWN_IVTR_DETAIL_DATA: { // EC_Inventory.LogInventoryPacket("OWN_IVTR_DETAIL_DATA", data, hostId); // Parse and store if (data != null && data.Length >= 6) { byte byPackage = data[0]; byte ivtrSize = data[1]; if (EC_IvtrItemUtils.Instance.TryParseInventoryDetail(data, out var pkg, out var size, out var items)) { var inv = GetInventory(pkg); if (inv != null) { inv.Resize(size); inv.RemoveAllItems(); if (items != null) { foreach (var it in items) { if (it != null && it.Slot >= 0 && it.Slot < size) inv.SetItem(it.Slot, it); } } } } // check if we got the item from the Equipment Pack. If so, we have to load the equipment items if (byPackage == InventoryConst.IVTRTYPE_EQUIPPACK) { UpdateEquipSkins(); } } break; } case CommandID.GET_OWN_MONEY: { if (data != null) { try { var money = GPDataTypeHelper.FromBytes(data); var ui = GameObject.FindFirstObjectByType(); if (ui == null) { var all = Resources.FindObjectsOfTypeAll(); if (all != null) { for (int i = 0; i < all.Length; i++) { var candidate = all[i]; if (candidate != null && candidate.gameObject.scene.IsValid()) { ui = candidate; break; } } } } if (ui != null) { ui.UpdateMoney(money.amount, money.max_amount); } else { BrewMonster.Scripts.Managers.EC_InventoryUI.CacheMoney(money.amount, money.max_amount); } } catch (Exception ex) { Debug.LogWarning($"[Inventory] Failed to parse GET_OWN_MONEY: {ex.Message}"); } } break; } case CommandID.PLAYER_CASH: { if (data != null) { try { var cash = GPDataTypeHelper.FromBytes(data); var ui = GameObject.FindFirstObjectByType(); if (ui == null) { var all = Resources.FindObjectsOfTypeAll(); if (all != null) { for (int i = 0; i < all.Length; i++) { var candidate = all[i]; if (candidate != null && candidate.gameObject.scene.IsValid()) { ui = candidate; break; } } } } if (ui != null) { ui.UpdateCash(cash.cash_amount); } else { BrewMonster.Scripts.Managers.EC_InventoryUI.CacheCash(cash.cash_amount); } } catch (Exception ex) { Debug.LogWarning($"[Inventory] Failed to parse PLAYER_CASH: {ex.Message}"); } } break; } } } public void OnMsgHstCorrectPos(in ECMSG Msg) { //Debug.LogError("HoangDev : OnMsgHstCorrectPos"); byte[] buf = (byte[])Msg.dwParam1; // chỗ bạn lưu pDataBuf GCHandle handle = GCHandle.Alloc(buf, GCHandleType.Pinned); cmd_host_correct_pos pCmd = (cmd_host_correct_pos)Marshal.PtrToStructure( handle.AddrOfPinnedObject(), typeof(cmd_host_correct_pos)); handle.Free(); //cmd_host_correct_pos pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); //Debug.LogError("HoangDev :pCmd.pos " + pCmd.pos); SetPos(pCmd.pos); m_vVelocity.Clear(); m_CDRInfo.vAbsVelocity.Clear(); m_MoveCtrl.SetMoveStamp(pCmd.stamp); } 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(); } public void OnMsgHstGoto(in ECMSG Msg) { PopupManager.Instance.OnPlayerRevived(); // p1 is a byte[] buffer; parse into cmd_notify_hostpos then set position byte[] buf = (byte[])Msg.dwParam1; cmd_notify_hostpos pCmd = GPDataTypeHelper.FromBytes(buf); SetPos(new Vector3(pCmd.vPos.x, pCmd.vPos.y, pCmd.vPos.z)); } private void OnMsgHstStartAttack(in ECMSG Msg) { cmd_host_start_attack pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); // ASSERT(pCmd); // test code... // g_pGame.GetRTDebug().OutputNotifyMessage(RTDCOL_WARNING, _AL("start attack !")); // Check whether target is the one that we have selected if (m_idSelTarget != pCmd.idTarget) // g_pGame.RuntimeDebugInfo(RTDCOL_WARNING, _AL("Target has changed !")); // If target turn to be un-attackable, cancel action if (AttackableJudge(pCmd.idTarget, true) == 0) { UnityGameSession.c2s_CmdCancelAction(); // g_pGame.RuntimeDebugInfo(RTDCOL_WARNING, _AL("Cannel attacking !")); return; } // Synchronize ammo amount // CECIvtrItem* pItem = m_pEquipPack.GetItem(EQUIPIVTR_PROJECTILE); // if (pItem) // { // if (!pCmd.ammo_remain) // m_pEquipPack.SetItem(EQUIPIVTR_PROJECTILE, NULL); // else // pItem.SetAmount(pCmd.ammo_remain); // } CECHPWorkMelee pWork = (CECHPWorkMelee)m_pWorkMan.CreateWork(Host_work_ID.WORK_HACKOBJECT); m_pWorkMan.StartWork_p1(pWork); m_bMelee = true; // AP_ActionEvent(AP_EVENT_STARTMELEE); } private void OnMsgHstStopAttack(in ECMSG Msg) { // using namespace S2C; // m_bMelee = false; // // // if there is an attack event currently, we should let it fire now. BMLogger.LogError("HoangDev: OnMsgHstStopAttack " + gameObject.name); ClearComActFlagAllRankNodes(true); cmd_host_stop_attack pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); // ASSERT(pCmd); /* If attack stopped for target is leave too far, trace it and continue attacking. Stop reason defined as below: 0x00: attack is canceled or host want to do some other things. 0x01: unable to attack anymore (no ammo, weapon is broken etc.) 0x02: invalid target (target missed or died) 0x04: target is out of range */ if ((pCmd.iReason & 0x04) != 0) { if (!m_pWorkMan.IsMovingToPosition() && !m_pWorkMan.IsTracing()) { if (CmdNormalAttack(false, false, 0, -1)) //m_pComboSkill != NULL { // AP_ActionEvent(AP_EVENT_MELEEOUTOFRANGE, 1); } else { m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_HACKOBJECT); // AP_ActionEvent(AP_EVENT_STOPMELEE); } } else { // AP_ActionEvent(AP_EVENT_STOPMELEE); } } else { m_pWorkMan.FinishRunningWork(Host_work_ID.WORK_HACKOBJECT); // AP_ActionEvent(AP_EVENT_STOPMELEE); } // #ifdef _SHOW_AUTOPOLICY_DEBUG // a_LogOutput(1, "Stop Attack, Reason = %d", pCmd.iReason); // #endif } // 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); m_idSelTarget = pCmd.idTarget; m_idUCSelTarget = 0; } else if (Convert.ToInt32(Msg.dwParam2) == CommandID.UNSELECT) { m_idSelTarget = 0; } } public void SetPos(Vector3 pos) { playerTransform.position = pos; m_aabb.Center = EC_Utility.ToA3DVECTOR3(pos) + new A3DVECTOR3(0.0f, m_aabb.Extents.y, 0.0f); m_aabb.CompleteMinsMaxs(); m_aabbServer.Center = EC_Utility.ToA3DVECTOR3(pos) + new A3DVECTOR3(0.0f, m_aabbServer.Extents.y, 0.0f); m_aabbServer.CompleteMinsMaxs(); } public void SetStatusRun(bool value) { if (!isGrounded) { Debug.LogError("Player not in ground"); return; } isRun = value; } public override void SetUpPlayer() { base.SetUpPlayer(); m_IncantCnt = new CECCounter(); m_IncantCnt.SetPeriod(1000); m_IncantCnt.Reset(true); } CECSkill GetNormalSkill(int id, bool bSenior = false /* false */) { CECSkill pSkill = null; if (ElementSkill.GetType((uint)id) == (byte)CECSkill.SkillType.TYPE_PASSIVE || ElementSkill.GetType((uint)id) == (byte)CECSkill.SkillType.TYPE_PRODUCE || ElementSkill.GetType((uint)id) == (byte)CECSkill.SkillType.TYPE_LIVE) pSkill = GetPassiveSkillByID(id, bSenior); else pSkill = GetPositiveSkillByID(id, bSenior); if (pSkill == null) // && m_pGoblin) { // This is a goblin skill for (int i = 0; i < m_aGoblinSkills.Count; i++) { if (m_aGoblinSkills[i].GetSkillID() == id) return m_aGoblinSkills[i]; } } if (pSkill == null) // may be target item skill { if (m_pTargetItemSkill != null && m_pTargetItemSkill.GetSkillID() == id) pSkill = m_pTargetItemSkill; } return pSkill; } public async void 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 SetPlayerModel(UnityGameSession.Instance.GetRoleInfo().occupation, UnityGameSession.Instance.GetRoleInfo().gender); Vector3 pos = new Vector3(role.pos.x, role.pos.y, role.pos.z); string roleName = Encoding.Unicode.GetString(UnityGameSession.Instance.GetRoleInfo().name.ByteArray); 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(OnMsgHstPushMove); 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_CDRInfo.vTPNormal = GroundCheck(out RaycastHit hit) ? hit.normal : Vector3.zero; m_CDRInfo.vExtent = m_aabbServer.Extents; 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); if (PopupManager.Instance != null) { PopupManager.Instance.OnPlayerDied(); } } else { 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); LoadResources(); 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; }*/ if (!CanDo(ActionCanDo.CANDO_MOVETO)) return; 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(OnMsgHstPushMove); } //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); } private bool NormalAttackObject(int idTarget, bool bForceAttack, bool bMoreClose = false) { if (idTarget == 0 || idTarget == m_PlayerInfo.cid) { // We should have check target isn't dead return false; } //if (!EC_Game.GetGameRun().GetWorld().GetObject(idTarget, 1)) // return false; bool bStartNewWork = false; bool bUseAutoPF = false; //CECPlayerWrapper* pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper(); //if (CECAutoPolicy::GetInstance().IsAutoPolicyEnabled() && pWrapper.GetAttackError() >= 2) //bUseAutoPF = true; CECHPWorkTrace pWorkTrace = null; CECHPWork pWork = null; if ((pWork = m_pWorkMan.GetWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT)) != null) { pWorkTrace = pWork as CECHPWorkTrace; } else if ((pWork = m_pWorkMan.GetWork(CECHPWork.Host_work_ID.WORK_HACKOBJECT)) != null) { if ((pWork as CECHPWorkMelee).GetTarget() == idTarget) return false; // Host is attacking the target pWorkTrace = (CECHPWorkTrace)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT); bStartNewWork = true; } else if (m_pWorkMan.CanStartWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT)) { pWorkTrace = (CECHPWorkTrace)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT); bStartNewWork = true; } if (pWorkTrace != null) { pWorkTrace.SetTraceTarget( pWorkTrace.CreatTraceTarget(idTarget, CECHPWorkTrace.Trace_reason.TRACE_ATTACK, bForceAttack), bUseAutoPF); pWorkTrace.SetMoveCloseFlag(bMoreClose); if (bStartNewWork) m_pWorkMan.StartWork_p1(pWorkTrace); return true; } return false; } public int AttackableJudge(int idTarget, bool bForceAttack) { if (CannotAttack()) return 0; //if (CDlgAutoHelp::IsAutoHelp()) // return 0; if (idTarget == 0 || idTarget == m_PlayerInfo.cid) return -1; CECObject pObject = EC_ManMessageMono.Instance.GetObject(idTarget, 1); if (!pObject) return -1; // If target is pet, it's attacked possibility depends on it's monster if (GPDataTypeHelper.ISNPCID(idTarget)) { CECNPC pNPC = (CECNPC)pObject; int idMaster = pNPC.GetMasterID(); if (idMaster != 0) { // master¿ÉÄÜÊÇhostplayer if (idMaster == m_PlayerInfo.cid) return 0; //// Follow pet cannot be attacked //if (pNPC.IsPetNPC() && ((CECPet)pNPC).IsFollowPet()) // return 0; idTarget = idMaster; pObject = EC_ManMessageMono.Instance.GetObject(idTarget, 1); if (!pObject) return -1; } } int iRet = 0; if (GPDataTypeHelper.ISNPCID(idTarget)) { CECNPC pNPC = (CECNPC)pObject; // If this npc is host's pet, cannot be attacked if (pNPC.GetMasterID() == m_PlayerInfo.cid) return 0; // If it's a pet and can not be attacked, pet can be attacked only if it's a fighting pet //if (pNPC.IsPetNPC() && !((CECPet)pNPC).CanBeAttacked()) // return 0; if (IsInBattle()) // Host is in battle { if (InSameBattleCamp(pNPC)) iRet = 0; else { if (pNPC.IsMonsterNPC()) iRet = 1; else if (pNPC.IsServerNPC() && (IsInFortress() || pNPC.GetRoleInBattle() == 8)) // ¶Ô·þÎñÐÍNPCµÄ¹¥»÷£¬°ïÅÉ»ùµØ»ò³Çսʱ¿ÉÓà iRet = 1; else iRet = 0; } } else if (pNPC.IsServerNPC()) { // In sanctuary we cannot attack NPCs if (!IsPVPOpen() || m_bInSanctuary || !bForceAttack) iRet = 0; else iRet = 1; } else // Is monster { iRet = 1; } if (iRet == 1 && pNPC.GetOwnerFaction() > 0) { // Õë¶Ô°ïÅÉ PVP Õ½ÕùÖнûÖ¹²¿·Ö¹¥»÷ if (GetFactionID() == pNPC.GetOwnerFaction() || // ²»¹¥»÷ͬ°ï¹Ö pNPC.IsFactionPVPMineCar() && !CanAttackFactionPVPMineCar() || // ÎÞ·¨ÔÙ¹¥»÷Ëû°ï¿ó³µÇé¿ö pNPC.IsFactionPVPMineBase() && !CanAttackFactionPVPMineBase()) { // ÎÞ·¨ÔÙ¹¥»÷Ëû°ï´æ¿óµãÇé¿ö iRet = 0; } } } // TO DO: fix later //else if (GPDataTypeHelper.ISPLAYERID(idTarget)) //{ // // Check duel at first // if (m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL && m_pvp.idDuelOpp == idTarget) // return 1; // else if (m_pvp.iDuelState == Duel_state.DUEL_ST_STOPPING && m_pvp.idDuelOpp == idTarget) // return 0; // // In sanctuary we cannot attack other players // if (m_bInSanctuary) // return 0; // //ASSERT(pObject.GetClassID() == CECObject::OCID_ELSEPLAYER); // EC_ElsePlayer pPlayer = (EC_ElsePlayer)pObject; // ROLEBASICPROP bp = pPlayer.GetBasicProps(); // EC_GAME_SETTING gs = g_pGame.GetConfigs().GetGameSettings(); // if (m_pvp.bFreePVP) // { // if (IsTeamMember(idTarget)) // return 0; // // In free pvp mode, for example, host is in arena. // if (bForceAttack) // iRet = 1; // else if (gs.bAtk_NoMafia && IsFactionMember(pPlayer.GetFactionID())) // iRet = 0; // else if (gs.bAtk_NoWhite && !pPlayer.IsInvader() && !pPlayer.IsPariah()) // iRet = 0; // else if (gs.bAtk_NoAlliance && g_pGame.GetFactionMan().IsFactionAlliance(pPlayer.GetFactionID())) // iRet = 0; // else if (gs.bAtk_NoForce && GetForce() > 0 && GetForce() == pPlayer.GetForce()) // iRet = 0; // else // iRet = 1; // } // else if (m_iBattleCamp != GP_BATTLE_CAMP_NONE) // { // // Host is in battle // int iCamp = pPlayer.GetBattleCamp(); // if (iCamp != GP_BATTLE_CAMP_NONE && iCamp != m_iBattleCamp) // iRet = 1; // else // iRet = 0; // } // else // Normal mode // { // if (IsTeamMember(idTarget)) // return 0; // if (!IsPVPOpen() || !pPlayer.IsPVPOpen() || m_BasicProps.iLevel < EC_MAXNOPKLEVEL || bp.iLevel < EC_MAXNOPKLEVEL) // iRet = 0; // else if (bForceAttack) // iRet = 1; // else if (!gs.bAtk_Player) // iRet = 0; // else if (gs.bAtk_NoMafia && IsFactionMember(pPlayer.GetFactionID())) // iRet = 0; // else if (gs.bAtk_NoWhite && !pPlayer.IsInvader() && !pPlayer.IsPariah()) // iRet = 0; // else if (gs.bAtk_NoAlliance && g_pGame.GetFactionMan().IsFactionAlliance(pPlayer.GetFactionID())) // iRet = 0; // else if (gs.bAtk_NoForce && GetForce() > 0 && GetForce() == pPlayer.GetForce()) // iRet = 0; // else // iRet = 1; // } //} else { return -1; } return iRet; } 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); } // Load configs data (shortcut, etc.) from specified buffer // Converted from: bool CECHostPlayer::LoadConfigData(const void* pDataBuf) public bool LoadConfigData(byte[] dataBuf) { if (dataBuf == null || dataBuf.Length < sizeof(uint)) return false; int offset = 0; // Version number uint dwVer = GPDataTypeHelper.FromBytes(dataBuf, offset); offset += sizeof(uint); if (dwVer > HostCfgConstants.HOSTCFG_VERSION) { return false; } // Load shortcut configs... int iHostSCSets1 = (dwVer <= 4) ? 3 : HostCfgConstants.NUM_HOSTSCSETS1; for (int i = 0; i < iHostSCSets1; i++) { if (offset >= dataBuf.Length) return false; if (m_aSCSets1[i] == null) { m_aSCSets1[i] = new CECShortcutSet(); m_aSCSets1[i].Init(HostCfgConstants.SIZE_HOSTSCSET1); } if (!m_aSCSets1[i].LoadConfigData(dataBuf, dwVer, ref offset)) return false; } for (int i = 0; i < HostCfgConstants.NUM_HOSTSCSETS2; i++) { if (offset >= dataBuf.Length) break; // No more data; tolerate truncated optional parts if (m_aSCSets2[i] == null) { m_aSCSets2[i] = new CECShortcutSet(); m_aSCSets2[i].Init(HostCfgConstants.SIZE_HOSTSCSET2); } if (!m_aSCSets2[i].LoadConfigData(dataBuf, dwVer, ref offset)) return false; } // Notes: // - Auto fashion sets, system module shortcut sets, booth packs, and AutoYinpiao // sections from native are not loaded here in this Unity port yet. // The native format appends these after the two shortcut-set groups. // We intentionally ignore them safely for now. return true; } public int GetCharacterID() { return m_PlayerInfo.cid; } public bool CannotAttack() { return (m_dwLIES & (uint)Logic_Influence_Extned_states.LIES_DISABLEFIGHT) != 0; } public bool CanTouchTarget(A3DVECTOR3 vHostPos, A3DVECTOR3 vTargetPos, float fTargetRad, int iReason, float fMaxCut = 1.0f) { float fDist = A3d_Magnitude(vTargetPos - vHostPos); switch (iReason) { case 1: // melee { float fRange; 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) return true; break; } case 2: // cast magic { if (m_pPrepSkill != null) { //TODO : Check this function GetCastRange float fRange = m_pPrepSkill.GetCastRange(m_ExtProps.ak.AttackRange, GetPrayDistancePlus()); if (fRange > 0.0f) { if (fDist - fTargetRad <= fRange) return true; } else return true; } break; } case 3: // talk { if (fDist - fTargetRad <= 5.0f) return true; break; } default: // no special reason { if (fDist < (fTargetRad + m_fTouchRad) * 3.0f) return true; break; } } 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) { A3DVECTOR3 vector = new A3DVECTOR3(playerTransform.position.x, playerTransform.position.y, playerTransform.position.z); 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 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.Raycast(startPoint, dir, out hit, layerGround)) { fTerrainHeight = Vector3.Distance(hit.point, startPoint); } float fWaterHeight = 0f; if (Physics.Raycast(startPoint, dir, out hit, layerWater)) { fWaterHeight = Vector3.Distance(hit.point, startPoint); } 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 = Math.Min(m_ExtProps.mv.run_speed, fSpeedSev); break; } return fSpeedSev; } public bool ApplySkillShortcut(int idSkill, bool bCombo = false /* false */, int idSelTarget = 0 /* 0 */, int iForceAtk = -1 /* -1 */) { //StackChecker::ACTrace(4); if (m_pActionSwitcher != null) m_pActionSwitcher.PostMessge((int)EMsgActionSwitcher.MSG_CASTSKILL); // Return-town skill is very special, handle it separately //if (idSkill == ID_RETURNTOWN_SKILL) // return ReturnToTargetTown(0, bCombo); //if (idSkill == ID_SUMMONPLAYER_SKILL) // return SummonPlayer(idSelTarget, bCombo); if (!CanDo(ActionCanDo.CANDO_SPELLMAGIC)) return false; if (InSlidingState()) return false; if (!bCombo) //ClearComboSkill(); if (idSelTarget == 0) idSelTarget = m_idSelTarget; CECSkill pSkill = GetPositiveSkillByID(idSkill); if (pSkill == null) pSkill = GetEquipSkillByID(idSkill); if (pSkill == null) pSkill = CECComboSkillState.Instance.GetInherentSkillByID((uint)idSkill); if (pSkill == null) { return false; } //// If we press a chargeable skill again when it's being charged, //// we cast it out at once if (IsSpellingMagic() && m_pCurSkill != null && m_pCurSkill.IsCharging() && m_pCurSkill.GetSkillID() == pSkill.GetSkillID()) { m_pCurSkill.EndCharging(); UnityGameSession.c2s_SendCmdContinueAction(); return true; } //int iCon = CheckSkillCastCondition(pSkill); //if (iCon) //{ // ProcessSkillCondition(iCon); // return false; //} //// Get force attack flag bool bForceAttack = false; if (iForceAtk < 0) bForceAttack = glb_GetForceAttackFlag(0); else bForceAttack = iForceAtk > 0 ? true : false; //// Check negative effect skill //if (pSkill.GetType() == (int)skill_type.TYPE_ATTACK || pSkill.GetType() == (int)skill_type.TYPE_CURSE) //{ // if (idSelTarget == m_PlayerInfo.cid) // { // // Host cannot spell negative effect magic to himself. // EC_Game.GetGameRun().AddFixedChannelMsg(FIXMSG_TARGETWRONG, GP_CHAT_FIGHT); // return false; // } // else if (idSelTarget != 0) // { // if (AttackableJudge(idSelTarget, bForceAttack) != 1) // return false; // } //} //// Check whether target type match int idCastTarget = idSelTarget; int iTargetType = pSkill.GetTargetType(); if (pSkill.GetType() == (int)skill_type.TYPE_BLESS || pSkill.GetType() == (int)skill_type.TYPE_NEUTRALBLESS) { if (iTargetType == 0 || !GPDataTypeHelper.ISPLAYERID(idSelTarget)) idCastTarget = m_PlayerInfo.cid; // In some case, we shouldn't add bless effect to other players if (GPDataTypeHelper.ISPLAYERID(idCastTarget) && idCastTarget != m_PlayerInfo.cid) { // If host has set bless skill filter only to himself, bless skill couldn't add to other players byte byBLSMask = EC_Utility.glb_BuildBLSMask(); if (pSkill.GetRangeType() == (int)range_type.RANGE_POINT) { if (!IsTeamMember(idCastTarget)) { if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_SELF) != 0) idCastTarget = m_PlayerInfo.cid; else { EC_ElsePlayer pPlayer = (EC_ElsePlayer)EC_ManMessageMono.Instance.GetECManPlayer.GetPlayer(idCastTarget); if (pPlayer == null) { // Ä¿±êÏûʧ return false; } if (pPlayer.IsInvader() || pPlayer.IsPariah()) { if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NORED) != 0) idCastTarget = m_PlayerInfo.cid; } if (!IsFactionMember(pPlayer.GetFactionID())) { if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOMAFIA) != 0) idCastTarget = m_PlayerInfo.cid; } if (!IsFactionAllianceMember(pPlayer.GetFactionID())) { if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOALLIANCE) != 0) idCastTarget = m_PlayerInfo.cid; } if (GetForce() != pPlayer.GetForce()) { if ((byBLSMask & (byte)PVPMask.GP_BLSMASK_NOFORCE) != 0) idCastTarget = m_PlayerInfo.cid; } } } } // If host is in duel, bless skill couldn't add to opponent if (IsInDuel() && idSelTarget == m_pvp.idDuelOpp) idCastTarget = m_PlayerInfo.cid; // If host is in battle, bless skill couldn't add to enemies if (IsInBattle()) { EC_ElsePlayer pPlayer = EC_ManMessageMono.Instance.GetECManPlayer.GetElsePlayer(idCastTarget); if (!InSameBattleCamp(pPlayer)) idCastTarget = m_PlayerInfo.cid; } } } /* else if (pSkill.GetType() == CECSkill::TYPE_BLESSPET) { CECPet* pPet = g_pGame->GetGameRun()->GetWorld()->GetNPCMan()->GetPetByID(idSelTarget); if (!pPet || pPet->GetMasterID() == GetCharacterID()) { // Spell skill on host's pet CECPetData* pPetData = m_pPetCorral->GetActivePet(); if (!pPetData || pPetData->GetClass() != GP_PET_CLASS_COMBAT && pPetData->GetClass() != GP_PET_CLASS_SUMMON && pPetData->GetClass() != GP_PET_CLASS_EVOLUTION) return false; idCastTarget = m_pPetCorral->GetActivePetNPCID(); } // Only fighting pet can be blessed. if (pPet && !pPet->CanBeAttacked()) return false; }*/ else { if (iTargetType != 0 && idCastTarget == 0) return false; } // iTargetType == 4 means target must be pet. The problem is that pet will // disappear from world after it died, so GetWorld()->GetObject() will return // NULL when host spells revive-pet skill on his dead pet. So, the target // type of revive-pet skill should be 0 if (iTargetType != 0) { // Target shoundn't be a corpse ? int iAliveFlag = 0; if (iTargetType == 1) iAliveFlag = 1; else if (iTargetType == 2) iAliveFlag = 2; /*CECObject pObject = EC_Game.GetGameRun().GetWorld().GetObject(idCastTarget, iAliveFlag); if (!pObject) return false;*/ } if (!IsMeleeing() && !IsSpellingMagic() && (iTargetType == 0 || idCastTarget == m_PlayerInfo.cid)) { // Cast this skill need't checking cast distance if (!pSkill.ReadyToCast()) return false; if (!pSkill.IsInstant() && pSkill.GetType() != (int)Skilltype.TYPE_FLASHMOVE) { /* if (!NaturallyStopMoving()) return false; */ // Couldn't stop naturally, so cancel casting skill } else if (pSkill.GetType() == (int)Skilltype.TYPE_FLASHMOVE) { if (!CanDo(ActionCanDo.CANDO_FLASHMOVE)) return false; } BMLogger.LogError("HoangDev: ApplySkillShortcut - PATH 1: Calling CastSkill (self-cast)"); m_pPrepSkill = pSkill; CastSkill(m_PlayerInfo.cid, bForceAttack); } else if (IsSpellingMagic() && m_pCurSkill == pSkill) { // If we are casting the same skill and it's in cooling time return false; } else // Have to trace selected object before cast skill { if (!pSkill.ReadyToCast()) return false; if (CECCastSkillWhenMove.Instance.IsSkillSupported(pSkill.GetSkillID(), this) && m_pWorkMan.IsMovingToPosition() && m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID())) { m_pPrepSkill = pSkill; return CastSkill(idCastTarget, bForceAttack); } else { bool bTraceOK = false; bool bUseAutoPF = false; /* CECPlayerWrapper pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper(); if (CECAutoPolicy::GetInstance().IsAutoPolicyEnabled() && pWrapper->GetAttackError() >= 2) bUseAutoPF = true;*/ if (idCastTarget == 0) { idCastTarget = GetCharacterID(); // ±ÜÃâË²ÒÆµÈ¼¼ÄÜʱ idCastTarget Ϊ0µ¼Ö CECWorkTrace::CreateTraceTarget ·µ»Ø¿Õ } CECHPWork pWork = m_pWorkMan.GetWork(Host_work_ID.WORK_TRACEOBJECT); if (pWork != null) { CECHPWorkTrace pWorkTrace = (CECHPWorkTrace)(pWork); if (pWorkTrace.GetTraceReason() == Trace_reason.TRACE_SPELL && pWorkTrace.GetTarget() == idCastTarget && pWorkTrace.GetPrepSkill() == pSkill) return false; // We are just doing the same thing pWorkTrace.SetTraceTarget( pWorkTrace.CreatTraceTarget(idCastTarget, Trace_reason.TRACE_SPELL, bForceAttack), bUseAutoPF); pWorkTrace.SetPrepSkill(pSkill); bTraceOK = true; } else if (m_pWorkMan.CanStartWork(Host_work_ID.WORK_TRACEOBJECT)) { CECHPWorkTrace pWork2 = (CECHPWorkTrace)m_pWorkMan.CreateWork(Host_work_ID.WORK_TRACEOBJECT); pWork2.SetTraceTarget( pWork2.CreatTraceTarget(idCastTarget, Trace_reason.TRACE_SPELL, bForceAttack), bUseAutoPF); pWork2.SetPrepSkill(pSkill); m_pWorkMan.StartWork_p1(pWork2); bTraceOK = true; } if (!bTraceOK) return false; // } //} } } return true; } public CECSkill GetPrepSkill() { return m_pPrepSkill; } 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; } } } public EC_Inventory GetPack() { return m_packInventory; } 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 CastSkill(int idTarget, bool bForceAttack, CECObject pTarget = null) { // Check if prep skill is valid, ready to cast, and not currently spelling magic if (m_pPrepSkill == null || !m_pPrepSkill.ReadyToCast() || IsSpellingMagic()) { // Check if skill can change to melee attack if (m_pPrepSkill != null && m_pPrepSkill.ChangeToMelee()) { bool bFlag = m_pPrepSkill.ReadyToCast(); // Finish any tracing work if (m_pWorkMan.IsTracing()) m_pWorkMan.FinishRunningWork(CECHPWork.Host_work_ID.WORK_TRACEOBJECT); // Handle combo skill or normal attack if (m_pComboSkill != null) m_pComboSkill.Continue(false); else { // Perform normal attack instead NormalAttackObject(idTarget, true); } } m_pPrepSkill = null; return false; } if (m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_ATTACK || m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_CURSE) { if (idTarget != 0 && AttackableJudge(idTarget, bForceAttack) != 1) { m_pPrepSkill = null; return false; } } //TODO: Check cast condition - method not yet implemented int iRet = CheckSkillCastCondition(m_pPrepSkill); if (iRet != 0) { switch (iRet) { case 2: // Need MP // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEEDMP); break; case 8: // Need AP // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEEDAP); break; case 10: // Pack full // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_PACKFULL1); break; case 20: // Need item // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEEDITEM); break; case 12: // HP unsatisfied // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_HP_UNSATISFIED); break; } m_pPrepSkill = null; return false; } byte byPVPMask = glb_BuildPVPMask(bForceAttack); Debug.Log($"HoangDev: Cast Skill ID={m_pPrepSkill.GetSkillID()}"); // Handle instant skills if (m_pPrepSkill.IsInstant()) { UnityGameSession.c2s_CmdCastInstantSkill(m_pPrepSkill.GetSkillID(), byPVPMask, 1, idTarget); m_pPrepSkill = null; } // Handle flash move skills (瞬移技能) else if (m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_FLASHMOVE) { // Self or self-sphere range types if (m_pPrepSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SLEF || m_pPrepSkill.GetRangeType() == (int)CECSkill.RangeType.RANGE_SELFSPHERE) { A3DVECTOR3 vDir = GetDir(); float fDist = m_pPrepSkill.GetCastRange(m_ExtProps.ak.AttackRange, GetPrayDistancePlus()); // 左侧之翼,左跳 (Left wing skill - jump left) if (m_pPrepSkill.GetSkillID() == 1844) { vDir = A3d_RotatePosAroundY(-vDir, Mathf.PI / 2); } // 右侧之翼,右跳 (Right wing skill - jump right) else if (m_pPrepSkill.GetSkillID() == 1845) { vDir = A3d_RotatePosAroundY(vDir, Mathf.PI / 2); } // 范围小于0则后跳 (If range < 0, jump backward) else if (fDist < 0.0f) { vDir = -vDir; } fDist = Mathf.Abs(fDist); A3DVECTOR3 vDest = m_MoveCtrl.FlashMove(vDir, 100.0f, fDist); BMLogger.LogError( $"HoangDev: skill id={m_pPrepSkill.GetSkillID()} , vDest={vDest} ,position = {transform.position}, byPVPMask={byPVPMask} "); UnityGameSession.c2s_CmdCastPosSkill(m_pPrepSkill.GetSkillID(), EC_Utility.ToVector3(vDest), byPVPMask, 0, 0); m_pPrepSkill = null; } else { // 刺客如影随行类技能 (Assassin shadow-following skills) bool bSuccess = false; while (true) { // Break if no target or self-target if (idTarget == 0 || idTarget == GetCharacterID()) break; // Get target object CECObject pObject = CECWorld.Instance.GetObject(idTarget, 0); if (pObject == null) break; A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(transform.position); // GetPos() A3DVECTOR3 vTargetPos = EC_Utility.ToA3DVECTOR3(pObject.transform.position); // pObject.GetPos() // 判断技能释放距离限制是否满足 (Check if skill cast distance is satisfied) float fTouchRadius = 0.0f; if (GPDataTypeHelper.ISNPCID(idTarget)) { CECNPC pNPC = pObject as CECNPC; if (pNPC != null) fTouchRadius = pNPC.GetTouchRadius(); else break; } else if (GPDataTypeHelper.ISPLAYERID(idTarget)) { EC_ElsePlayer pElsePlayer = pObject as EC_ElsePlayer; if (pElsePlayer != null) fTouchRadius = pElsePlayer.GetTouchRadius(); else break; } else break; if (!CanTouchTarget(vTargetPos, fTouchRadius, 2)) { // Target is far - show message // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_TARGETISFAR); Debug.Log("Target is too far"); break; } A3DVECTOR3 vMoveDir = vTargetPos - vHostPos; float fDist = EC_Utility.ToVector3(vMoveDir).magnitude; // 距离目标太近,不处理 (Too close to target, don't process) float fNearDist = 0.0f; // TODO: Implement IsTooNear if (IsTooNear(vTargetPos, ref fNearDist)) { Debug.Log("Target is too near"); break; } // 计算要移往的目标位置(默认值) (Calculate target position to move to) vMoveDir.Normalize(); A3DVECTOR3 vMovePos = vHostPos + vMoveDir * (fDist - fNearDist); // TODO: Implement ClampAboveGround float fClampedHeight = ClampAboveGround(vMovePos); if (Mathf.Abs(fClampedHeight - vMovePos.y) >= 5.0f) { Debug.Log("Would stuck or so"); break; } vMovePos.y = fClampedHeight; bool bPosVerified = false; // 目标为带凸包的 NPC 时,单独处理 (Special handling for NPCs with collision) if (GPDataTypeHelper.ISNPCID(idTarget)) { // TODO: Implement CalcCollideFreePos for NPC AABB CECNPC pNPC = pObject as CECNPC; A3DAABB aabbNPC = new A3DAABB(); if (pNPC.GetCHAABB(ref aabbNPC)) { A3DVECTOR3 vTestPos; if (CalcCollideFreePos(aabbNPC, out vTestPos)) { vMovePos = vTestPos; bPosVerified = true; } else { Debug.Log("Would stuck or so"); break; } } } // TODO: Implement collision checking if (!bPosVerified && !IsPosCollideFree(vMovePos)) { A3DVECTOR3 vTestPos2; if (!CalcVerticalCollideFreePos(vMovePos, out vTestPos2)) { Debug.Log("Would stuck or so"); break; } vMovePos = vTestPos2; bPosVerified = true; } //TODO: Implement IsTooNear check for final position float reffake = 0; if (IsTooNear(vMovePos, ref reffake)) { Debug.Log("Target is too near"); break; } // 发送协议 (Send protocol) UnityGameSession.c2s_CmdCastPosSkill(m_pPrepSkill.GetSkillID(), EC_Utility.ToVector3(vMovePos), byPVPMask, 1, idTarget); bSuccess = true; } m_pPrepSkill = null; return bSuccess; } } else { // Regular skill casting byte byPVPMask2 = glb_BuildPVPMask(bForceAttack); UnityGameSession.c2s_CmdCastSkill(m_pPrepSkill.GetSkillID(), byPVPMask2, 1, idTarget); } 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?.EquipInventory; 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) { //BMLogger.LogError("HoangDev: HostPlayer SelectTarget"); 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(); } else { //BMLogger.LogError("HoangDev: HostPlayer setlect npc"); UnityGameSession.c2s_CmdSelectTarget(idTarget); } } return bRet; } CECSkill GetPassiveSkillByID(int id, bool bSenior /* false */) { CECSkill pSenior = null; for (int i = 0; i < m_aPsSkills.Count; i++) { if (m_aPsSkills[i].GetSkillID() == id) return m_aPsSkills[i]; else if (m_aPsSkills[i].GetJunior().Find((uint)id)) pSenior = m_aPsSkills[i]; } if (bSenior && pSenior != null) return pSenior; return null; } public CECSkill GetPositiveSkillByID(int id, bool bSenior = false) { CECSkill pSenior = null; for (int i = 0; i < m_aPtSkills.Count; i++) { if (m_aPtSkills[i].GetSkillID() == id) return m_aPtSkills[i]; else if (m_aPtSkills[i].GetJunior().Find((uint)id)) pSenior = m_aPtSkills[i]; } if (bSenior && pSenior != null) return pSenior; return null; } // C# conversion of CECHostPlayer::GetEquipSkillByID // Assumes: GetEquipSkillNum() returns the count of equipment skills // GetEquipSkillByIndex(int) returns a CECSkill at the given index public CECSkill GetEquipSkillByID(int id) { CECSkill pRet = null; for (int i = 0; i < GetEquipSkillNum(); i++) { CECSkill pSkill = GetEquipSkillByIndex(i); if (pSkill != null && pSkill.GetSkillID() == id) { pRet = pSkill; break; } } return pRet; } public int GetEquipSkillNum() { return m_aEquipSkills.Count; } public CECSkill GetEquipSkillByIndex(int n) { return m_aEquipSkills[n]; } // Check skill cast condition // Returns: 0 if success, error code otherwise // Error codes: 1=invalid weapon, 2=need mp, 3=invalid state, 6=target wrong, 7=invalid state, // 8=need ap, 9=not enough ammo, 10=pack full, 11=invalid env, 12=hp unsatisfied, // 13=combo skill not active, 20=need item public int CheckSkillCastCondition(CECSkill pSkill) { // Check if skill requires an item if (pSkill.SkillCore != null) { int idItem = pSkill.SkillCore.GetItemCost(); if (idItem > 0 && GetPack().GetItemTotalNum(idItem) <= 0) { return 20; // Need item } } // Check combo skill prerequisite (for night shadow continuous skills) // Note: GetComboSkPreSkill and IsActiveComboSkill methods need to be implemented // TODO: Implement GetComboSkPreSkill in ElementSkill/CECSkill // TODO: Implement IsActiveComboSkill in CECComboSkillState /* if (pSkill.GetComboSkPreSkill() != 0) { if (!CECComboSkillState.Instance.IsActiveComboSkill(pSkill.GetSkillID())) { return 13; // Combo skill not active } } */ // Build UseRequirement info UseRequirement Info = new UseRequirement(); Info.mp = m_BasicProps.iCurMP; Info.ap = m_BasicProps.iCurAP; Info.form = m_iShape; // Different from PW, no need to mask Info.freepackage = m_packInventory.GetEmptySlotNum(); Info.move_env = GetMoveEnv(); Info.is_combat = IsFighting(); Info.hp = m_BasicProps.iCurHP; Info.max_hp = m_ExtProps.bs.max_hp; // Info.combo_state = CECComboSkillState.Instance.GetComboSkillState(); // TODO: Implement GetComboSkillState // Get weapon's major class ID // Equipment inventory slot constants from CECPlayer.IndexOfIteminEquipmentInventory const int EQUIPIVTR_WEAPON = 0; // Weapon slot const int EQUIPIVTR_PROJECTILE = 4; // Projectile slot (arrows) Info.weapon = 0; Info.arrow = 0; EC_IvtrItem pWeaponItem = m_equipInventory.GetItem(EQUIPIVTR_WEAPON); if (pWeaponItem != null && pWeaponItem is EC_IvtrEquip pWeaponEquip) { // Check if weapon has endurance if (pWeaponEquip.CurEndurance > 0) { // TODO: Implement CanUseEquipment method to check level/class requirements // For now, use the template ID as weapon type Info.weapon = pWeaponItem.GetTemplateID(); } } // Get remaining arrow number EC_IvtrItem pArrowItem = m_equipInventory.GetItem(EQUIPIVTR_PROJECTILE); if (pArrowItem != null) { // TODO: Implement CanUseProjectile method to check requirements // For now, use the item count Info.arrow = pArrowItem.GetCount(); } // Call ElementSkill Condition check if (pSkill.SkillCore != null) { return pSkill.SkillCore.Condition(Info); } return 0; // Success } // Process skill condition error - display appropriate message // Returns: true if message was displayed, false otherwise public bool ProcessSkillCondition(int iCon) { int iMsg = -1; switch (iCon) { case 1: iMsg = (int)FixedMsg.FIXMSG_INVALIDWEAPON; break; case 2: iMsg = (int)FixedMsg.FIXMSG_NEEDMP; break; case 6: iMsg = (int)FixedMsg.FIXMSG_TARGETWRONG; break; case 3: iMsg = (int)FixedMsg.FIXMSG_SKILL_INVALIDSTATE; break; case 7: iMsg = (int)FixedMsg.FIXMSG_SKILL_INVALIDSTATE; break; case 8: iMsg = (int)FixedMsg.FIXMSG_NEEDAP; break; case 9: iMsg = (int)FixedMsg.FIXMSG_NOTENOUGHAMMO; break; case 10: iMsg = (int)FixedMsg.FIXMSG_PACKFULL1; break; case 11: iMsg = (int)FixedMsg.FIXMSG_SKILL_INVALIDENV; break; case 20: iMsg = (int)FixedMsg.FIXMSG_NEEDITEM; break; case 12: iMsg = (int)FixedMsg.FIXMSG_HP_UNSATISFIED; break; } if (iMsg >= 0) { // TODO: Implement AddFixedChannelMsg or use existing message system // EC_Game.GetGameRun().AddFixedChannelMsg(iMsg, GP_CHAT_FIGHT); Debug.LogWarning($"Skill condition failed: {iMsg}"); } return iMsg >= 0; } bool CanSelectTarget(int idTarget) { if (idTarget == 0 || idTarget == this.GetCharacterID()) { // 0 means unselect 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; } 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 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 || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsRidingOnPet() || GetShapeType() == 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() || 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 || IsRidingOnPet() || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()*/) bRet = false; break; case ActionCanDo.CANDO_ASSISTSEL: if (IsDead() || !GPDataTypeHelper.ISPLAYERID(m_idSelTarget) || m_idSelTarget == m_PlayerInfo.cid /*|| !m_pTeam || !m_pTeam.GetMemberByID(m_idSelTarget) || m_iBuddyId || IsPassiveMove() || m_playerLimits.test(PLAYER_LIMIT_NOCHANGESELECT)*/) bRet = false; break; case ActionCanDo.CANDO_FLY: if (IsDead() || IsRooting() /*|| IsSitting() || IsTrading() || IsReviving() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || GetBoothState() != 0 || IsFlashMoving() */ || m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan.Work_priority.PRIORITY_2) /*|| m_bHangerOn || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsRidingOnPet() || GetShapeType() == PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() || m_playerLimits.test(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() || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()*/) bRet = false; break; case ActionCanDo.CANDO_TRADE: if (IsDead() || IsMeleeing() /*|| IsAboutToDie() || IsSitting() || IsJumping() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || IsSpellingMagic() || GetBoothState() != 0 || m_iBuddyId || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsInvisible() || IsPassiveMove()*/) bRet = false; break; case ActionCanDo.CANDO_PLAYPOSE: if (IsDead() || IsMeleeing() || /*|| IsAboutToDie() || IsSitting() || IsJumping() || /* || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || IsSpellingMagic() || IsShapeChanged() || IsReviving() ||*/ m_iMoveEnv != (int)MoveEnvironment.MOVEENV_GROUND /*|| GetBoothState() != 0 || m_iBuddyId || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsRidingOnPet() || GetShapeType() == PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() || m_BattleInfo.IsChariotWar()*/ ) bRet = false; break; //case ActionCanDo.CANDO_SPELLMAGIC: // if (IsDead() || ISMATTERID(m_idSelTarget) || IsAboutToDie() || IsSitting() || // IsJumping() || IsFlashMoving() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || // IsChangingFace() || CannotAttack() || IsReviving() || GetBoothState() != 0 || // m_iBuddyId || IsRidingOnPet() || IsOperatingPet() || 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() || 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 || IsInvisible() || IsGMInvisible() || IsOperatingPet() || 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() == PLAYERMODEL_DUMMYTYPE2)) || IsJumpInWater() || m_iMoveEnv == Move_environment.MOVEENV_AIR || IsSitting() || IsMeleeing() || IsTrading() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || IsReviving() || IsSpellingMagic() || IsPicking() || IsGathering() || IsRooting() || GetBoothState() != 0 || m_bHangerOn || /*(IsJumping() && IsRidingOnPet()) ||*/ /*IsOperatingPet() || 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() || 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 || IsOperatingPet() || 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.HasWorkRunningOnPriority(CECHPWorkMan::PRIORITY_2) || // m_iBuddyId || IsOperatingPet() || 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.IsStanding() || m_iBuddyId || // IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || GetShapeType() == PLAYERMODEL_DUMMYTYPE2 || IsPassiveMove() || // m_playerLimits.test(PLAYER_LIMIT_NOBIND)) // bRet = false; // break; //case ActionCanDo.CANDO_DUEL: // if (IsDead() || IsAboutToDie() || IsSitting() || IsFighting() || IsTrading() || // IsReviving() || IsUsingTrashBox() || IsTalkingWithNPC() || IsChangingFace() || // GetBoothState() != 0 || m_iBuddyId || m_pvp.iDuelState != DUEL_ST_NONE || // IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || IsPassiveMove()) // bRet = false; // break; case ActionCanDo.CANDO_CHANGESELECT: //if (m_playerLimits.test(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 || IsOperatingPet() || IsRebuildingPet() || IsUsingItem() || // GetShapeType() == 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) { return false; } if (m_iMoveEnv == Move_environment.MOVEENV_AIR) { } else if (m_iMoveEnv == Move_environment.MOVEENV_WATER) { } 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() { return true; //A3DVECTOR3 vPos = GetPos(); //if (vPos.y < EC_Game.GetGameRun().GetWorld().GetWaterHeight(vPos) - m_MoveConst.fShoreDepth) // return false; //else // return true; } bool IsUsingItem() { return m_pWorkMan.IsUsingItem(); } bool IsPassiveMove() { return m_pWorkMan.IsPassiveMoving(); } public void SetGroundInfoClient() { isGrounded = GroundCheck(out lastGroundHit); m_GndInfo.bOnGround = isGrounded; } public void SetRotationHP(Vector3 dir) { transform.rotation = Quaternion.LookRotation(dir); } 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 SetSelectedTarget(int id) { m_idSelTarget = id; } public bool glb_GetForceAttackFlag(uint pdwParam) { /*bool bForceAttack = false; CECInputCtrl* pInputCtrl = g_pGame.GetGameRun().GetInputCtrl(); if (pdwParam) bForceAttack = pInputCtrl.IsCtrlPressed(*pdwParam); else bForceAttack = pInputCtrl.KeyIsBeingPressed(VK_CONTROL); return bForceAttack;*/ return false; } //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; //} private void OnMsgHstNPCGreeting(ECMSG Msg) { cmd_npc_greeting pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); if (GPDataTypeHelper.ISNPCID(pCmd.idObject)) { // רÃÅ´¦Àíѧϰ¼¼ÄܵÄÒþ²ØNPC //if (CECHostSkillModel::Instance().IsSkillLearnNPC(pCmd.idObject)) //{ // CECGameUIMan* pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan(); // //m_idSevNPC = pCmd.idObject; // //m_bTalkWithNPC = true; // //pGameUI.GetDialog("Win_SkillAction").Show(true); // //CDlgSkillAction* dlg = dynamic_cast(pGameUI.GetDialog("Win_SkillAction")); // //dlg.ForceShowDialog(); // CDlgSkillAction* dlg = dynamic_cast(pGameUI.GetDialog("Win_SkillAction")); // dlg.SetReceivedNPCGreeting(true); // return; //} CECNPC pNPC = EC_ManMessageMono.Instance.CECNPCMan.GetNPC(pCmd.idObject); if (!pNPC || !pNPC.IsServerNPC()) { return; } // Check distance again if (!CanTouchTarget(pNPC.GetPos(), pNPC.GetTouchRadius(), 3)) return; m_idSevNPC = pCmd.idObject; m_bTalkWithNPC = true; // Check way point service on NPC //TODO: Fix later //var dwID = (pNPC as CECNPCServer).GetWayPointID(); //if (dwID != null && !HasWayPoint(dwID)) //UnityGameSession.c2s_CmdNPCSevWaypoint(); //g_pGame.GetGameSession().c2s_CmdNPCSevWaypoint(); var pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan(); NPC_ESSENCE? result = (pNPC as CECNPCServer).GetDBEssence(); if (result != null) { pGameUI.PopupNPCDialog(result.Value); } } //else if (GPDataTypeHelper.ISPLAYERID(pCmd.idObject)) //{ // EC_ElsePlayer pPlayer = m_pPlayerMan.GetElsePlayer(pCmd.idObject); // // Check distance again // if (!pPlayer || !CanTouchTarget(pPlayer.GetPos(), 0.0f, 3)) // return; // m_idSevNPC = pCmd.idObject; // m_bTalkWithNPC = true; // m_iBoothState = 3; // g_pGame.GetGameSession().c2s_CmdNPCSevGetContent(GP_NPCSEV_BOOTHSELL); // m_pBuyPack.RemoveAllItems(); // m_pSellPack.RemoveAllItems(); // m_pEPBoothBPack.RemoveAllItems(); // m_pEPBoothSPack.RemoveAllItems(); // CECGameUIMan* pGameUI = g_pGame.GetGameRun().GetUIManager().GetInGameUIMan(); // pGameUI.PopupBoothDialog(true, false, pCmd.idObject); //} else { return; } } // 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; } void OnMsgHstWayPoint(ECMSG Msg) { //CECGameUIMan pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan(); //if (Convert.ToInt32(Msg.dwParam2) == CommandID.ACTIVATE_WAYPOINT) //{ // cmd_activate_waypoint pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); // m_aWayPoints.Add(pCmd.waypoint); // // add to waypoints array // pGameUI.GetMapDlgsMgr().UpdateWayPoints(&pCmd.waypoint, 1, false); // // Print a notify message // const CECMapDlgsMgr::PointMap& aWayPoints = pGameUI.GetMapDlgsMgr().GetTransPoint(); // CECMapDlgsMgr::PointMap::const_iterator itr = aWayPoints.find(pCmd.waypoint); // if(itr != aWayPoints.end()) // { // g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEWWAYPOINT, (itr.second).strName); // bool bCanPopUITips = true; // int count = CECUIConfig::Instance().GetGameUI().GetTaskIDDisableWayPointsUITipsCount(); // // ¼ì²éÉíÉÏÊÇ·ñÓнûÖ¹µ¯³ötipsµÄÈÎÎñ // for (int i=0;i 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()); 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) { if (m_pHoverGFX.GetState() == GFX_STATE.ST_STOP) { m_pHoverGFX.Play(); m_pHoverGFX.transform.parent = pObject.transform; m_pHoverGFX.transform.localPosition = Vector3.zero; } } else m_pHoverGFX.Stop(true); } else m_pHoverGFX.Stop(true); } if (m_pSelectedGFX) { if (!IsChangingFace() && (GPDataTypeHelper.ISPLAYERID(m_idSelTarget) || GPDataTypeHelper.ISNPCID(m_idSelTarget))) { var pObject = EC_ManMessageMono.Instance?.GetObject(m_idSelTarget, 1); if (pObject) { if (m_pSelectedGFX.GetState() == GFX_STATE.ST_STOP) { m_pSelectedGFX.Play(); 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); } // 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 // // Popup notify bubble text // BubbleText(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); } // Start normal attacking to selected target public bool CmdNormalAttack(bool bMoreClose /* false */, bool bCombo /* false */, int idTarget /* 0 */, int iForceAtk /* -1 */) { // StackChecker::ACTrace(2); // first of all see if we need to cancel sitdown work. // if (m_pWorkMan.IsSitting()) // { // g_pGame.GetGameSession().c2s_CmdStandUp(); // return false; // } if (!CanDo(ActionCanDo.CANDO_MELEE)) return false; // if (InSlidingState()) // return false; // if (!bCombo) // ClearComboSkill(); if (idTarget <= 0) idTarget = m_idSelTarget; bool bForceAttack; if (iForceAtk < 0) bForceAttack = glb_GetForceAttackFlag(0); else bForceAttack = iForceAtk > 0 ? true : false; if (AttackableJudge(idTarget, bForceAttack) != 1) return false; return NormalAttackObject(idTarget, bForceAttack, bMoreClose); } 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 ? 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 ? bool IsTalkingWithNPC() { return m_bTalkWithNPC; } // Is reviving bool IsReviving() { return m_pWorkMan.IsReviving(); } // Is spelling magic bool IsSpellingMagic() { 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 bool IsGathering() { 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 using item ? // Estimate move environment void EstimateMoveEnv(A3DVECTOR3 vPos) { if (IsFlying()) { m_iMoveEnv = Move_environment.MOVEENV_AIR; return; } CECWorld pWorld = EC_Game.GetGameRun().GetWorld(); LayerMask layerMaskTerrain = 1 << 6; LayerMask layerMaskBush = 1 << 7; 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; //m_GndInfo.fWaterHei = pWorld.GetWaterHeight(vTestPos); 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 (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 == MOVEENV_WATER) //{ // m_CDRInfo.fYVel = 0.0f; // if (m_iMoveMode == MOVE_SLIDE) // { // if (m_pWorkMan.IsMoving()) // m_iMoveMode = MOVE_MOVE; // else // m_iMoveMode = 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.Raycast(vPos, (vPos + Vector3.down), out hit, layerMaskTerrain)) { vTerrainPos = hit.point; vTerrainNormal = hit.normal; } if (Physics.Raycast(vPos, (vPos + Vector3.down), out hit, DeltaY, layerMaskTerrain)) { 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; if (!Physics.BoxCast(vCenter, vExt, (vCenter + Vector3.down).normalized, out hit, transform.rotation, DeltaY, layerMask)) { vHitPos = vCenter; vHitNormal = EC_Utility.ToA3DVECTOR3(Vector3.up); } else { vHitPos = hit.point; vHitNormal = EC_Utility.ToA3DVECTOR3(hit.normal); } } void OnMsgHstExtProp(ECMSG Msg) { cmd_own_ext_prop pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); m_ExtProps = pCmd.prop; m_BasicProps.iStatusPt = (int)pCmd.status_point; m_BasicProps.iAtkDegree = pCmd.attack_degree; m_BasicProps.iDefDegree = pCmd.defend_degree; m_BasicProps.iCritRate = pCmd.crit_rate; m_BasicProps.iCritDamageBonus = pCmd.crit_damage_bonus; m_BasicProps.iInvisibleDegree = pCmd.invisible_degree; m_BasicProps.iAntiInvisibleDegree = pCmd.anti_invisible_degree; m_BasicProps.iPenetration = pCmd.penetration; m_BasicProps.iResilience = pCmd.resilience; m_BasicProps.iVigour = pCmd.vigour; } private void OnMsgPlayerDoEmote(ECMSG Msg) { if (!m_pWorkMan.IsStanding() && !m_pWorkMan.IsBeingBound()) return; int cmd = Convert.ToInt32(Msg.dwParam2); if (cmd == CommandID.OBJECT_DO_EMOTE) { cmd_object_do_emote pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); DoEmote(pCmd.emotion); // if( m_iBuddyId ) // { // CECPlayer pBuddy = m_pPlayerMan->GetPlayer(m_iBuddyId); // if (pBuddy) // pBuddy->DoEmote(pCmd->emotion); // } GetTaskInterface().SetEmotion(pCmd.emotion); } else if (cmd == CommandID.OBJECT_EMOTE_RESTORE) { CECHPWork pWork = m_pWorkMan.GetRunningWork((int)WorkID.WORK_STAND); if (pWork != null) { ((CECHPWorkStand)pWork).SetPoseAction((int)PLAYER_ACTION_TYPE.ACT_STAND, false); } } } // Do emote action private bool DoEmote(int idEmote) { if (!m_pWorkMan.IsStanding()) return false; CECHPWorkStand pWork = m_pWorkMan.GetRunningWork(Host_work_ID.WORK_STAND) as CECHPWorkStand; if (pWork == null) { Debug.LogError("Null CECHPWorkStand"); } int iAction = (int)PLAYER_ACTION_TYPE.ACT_STAND; bool bSession = false; // Select action according to pose switch (idEmote) { case (int)RoleExpression.ROLEEXP_WAVE: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_WAVE; break; case (int)RoleExpression.ROLEEXP_NOD: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_NOD; break; case (int)RoleExpression.ROLEEXP_SHAKEHEAD: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_SHAKEHEAD; break; case (int)RoleExpression.ROLEEXP_SHRUG: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_SHRUG; break; case (int)RoleExpression.ROLEEXP_LAUGH: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_LAUGH; break; case (int)RoleExpression.ROLEEXP_ANGRY: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ANGRY; break; case (int)RoleExpression.ROLEEXP_STUN: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_STUN; break; case (int)RoleExpression.ROLEEXP_DEPRESSED: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_DEPRESSED; break; case (int)RoleExpression.ROLEEXP_KISSHAND: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_KISSHAND; break; case (int)RoleExpression.ROLEEXP_SHY: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_SHY; break; case (int)RoleExpression.ROLEEXP_SALUTE: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_SALUTE; break; case (int)RoleExpression.ROLEEXP_SITDOWN: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_SITDOWN; bSession = true; break; case (int)RoleExpression.ROLEEXP_ASSAULT: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ASSAULT; break; case (int)RoleExpression.ROLEEXP_THINK: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_THINK; break; case (int)RoleExpression.ROLEEXP_DEFIANCE: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_DEFIANCE; break; case (int)RoleExpression.ROLEEXP_VICTORY: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_VICTORY; break; case (int)RoleExpression.ROLEEXP_GAPE: iAction = (int)PLAYER_ACTION_TYPE.ACT_GAPE; break; case (int)RoleExpression.ROLEEXP_KISS: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_KISS; break; case (int)RoleExpression.ROLEEXP_FIGHT: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_FIGHT; break; case (int)RoleExpression.ROLEEXP_ATTACK1: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ATTACK1; break; case (int)RoleExpression.ROLEEXP_ATTACK2: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ATTACK2; break; case (int)RoleExpression.ROLEEXP_ATTACK3: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ATTACK3; break; case (int)RoleExpression.ROLEEXP_ATTACK4: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_ATTACK4; break; case (int)RoleExpression.ROLEEXP_DEFENCE: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_DEFENCE; break; case (int)RoleExpression.ROLEEXP_FALL: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_FALL; break; case (int)RoleExpression.ROLEEXP_FALLONGROUND: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_FALLONGROUND; break; case (int)RoleExpression.ROLEEXP_LOOKAROUND: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_LOOKAROUND; break; case (int)RoleExpression.ROLEEXP_DANCE: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_DANCE; break; case (int)RoleExpression.ROLEEXP_FASHIONWEAPON: iAction = (int)PLAYER_ACTION_TYPE.ACT_EXP_FASHIONWEAPON; break; case (int)RoleExpression.ROLEEXP_TWO_KISS: iAction = (int)PLAYER_ACTION_TYPE.ACT_TWO_KISS; break; case (int)RoleExpression.ROLEEXP_FIREWORK: iAction = (int)PLAYER_ACTION_TYPE.ACT_ATTACK_TOSS; break; default: break; } pWork.SetPoseAction(iAction, bSession); return true; } // Get cool time public int GetCoolTime(int iIndex, ref int piMax /* NULL */) { // if (iIndex >= 0 && iIndex < 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; } void OnMsgHstTargetIsFar(ECMSG Msg) { // TO DO: fix later //if(CmdNormalAttack(true, m_pComboSkill != null, 0, -1) ) // AP_ActionEvent(AP_EVENT_MELEEOUTOFRANGE, 1); if (CmdNormalAttack(true, false, 0, -1)) { //AP_ActionEvent(AP_EVENT_MELEEOUTOFRANGE, 1); } } 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 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; } }; 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; } } // 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; } } }