using BrewMonster; using BrewMonster.Managers; using BrewMonster.Network; using BrewMonster.Scripts; using BrewMonster.Scripts.Skills; using CSNetwork; using CSNetwork.GPDataType; using CSNetwork.Protocols; using CSNetwork.Protocols.rpcdata; using CSNetwork.Protocols.RPCData; using ModelRenderer.Scripts.Common; using System; using System.Data; using System.Text; using BrewMonster.PerfectWorld.Scripts.Vfx; using Cysharp.Threading.Tasks; using PerfectWorld.Scripts; using PerfectWorld.Scripts.Managers; using UnityEngine; using BrewMonster.Scripts.Managers; using System.Collections.Generic; using static BrewMonster.CECHostPlayer; using TMPro; using BrewMonster.PerfectWorld.Scripts.UI; namespace BrewMonster { public class EC_ElsePlayer : CECPlayer { public int m_iAppearFlag; // Player's appearing flag public bool m_bBaseInfoReady; // true, Basic info is ready public bool m_bCustomReady; // true, Customized data is ready public bool m_bEquipReady; // true, Equipment data is ready A3DVECTOR3 m_vMoveDir; // Player's velocity public A3DVECTOR3 m_vServerPos; // Player's real position on server A3DVECTOR3 m_vStopDir; // The direction when player stop moving // 是否是依附者 = Is it a dependent/attacher? //bool m_bHangerOn; // 依附者或被依附者id = The ID of the attacher (dependent) or the attached target. //int m_iBuddyId; bool m_bStopMove; // Stop move flag public const float MAX_LAGDIST = 10.0f; // Maximum lag distance //A3DVECTOR3 g_vAxisY = new A3DVECTOR3(0.0f, 1.0f, 0.0f); long m_dwLastMoveTime = 0; // Last move command arrived time float m_fMoveSpeed; // Move speed OtherPlayer_Move_Info m_cdr = new OtherPlayer_Move_Info(); float m_fDistToHost = 0f; // Distance to host player float m_fDistToHostH = 0f; // Horizontal distance to host player private BaseVfxObject m_pAppearGFX; // Appear GFX public CECCounter m_FightCnt; CECEPWorkMan m_pEPWorkMan; private CECEPWork _workStand; bool m_bLoadingModel = false; // Model loading flag bool m_bUseHintModel = false; // true, use hint model long m_i64NewEqpMask; // New equipment mask int[] m_aNewEquips = new int[InventoryConst.SIZE_ALL_EQUIPIVTR]; // New equipment item ID array public float m_fLastSpeed = 0f; // Move speed of last time CECHostPlayer pHost => EC_ManMessageMono.Instance?.GetECManPlayer?.GetHostPlayer(); public void Init(info_player_1 Info, int iAppearFlag) { SetUpPlayer(); m_iCID = (int)CECObject.Class_ID.OCID_ELSEPLAYER; m_dwResFlags = (uint)PlayerResourcesReadyFlag.RESFG_ALL; m_pEPWorkMan = new CECEPWorkMan(this); CalcPlayerAABB(); SetPlayerInfor(new INFO(Info.cid, Info.crc_e, Info.crc_c)); // Start a work do nothing will avoid some errors _workStand = new CECEPWorkStand(m_pEPWorkMan); m_pEPWorkMan.StartWork((int)WorkType.WT_NOTHING, ref _workStand); SetServerPos(Info.pos); SetPos(Info.pos); m_cdr.fStepHeight = m_MoveConst.fStepHei; m_cdr.vExts = m_aabbServer.Extents; m_cdr.vVelocity.Clear(); m_FightCnt = new CECCounter(); m_FightCnt.SetPeriod(15000); m_FightCnt.Reset(true); A3DVECTOR3 vPos = GetPos(); m_aabb.Center = vPos + new A3DVECTOR3(0.0f, m_aabb.Extents.y, 0.0f); m_aabb.CompleteMinsMaxs(); m_aabbServer.Center = vPos + new A3DVECTOR3(0.0f, m_aabbServer.Extents.y, 0.0f); m_aabbServer.CompleteMinsMaxs(); LoadAppearGfx(); } public void MoveTo(cmd_object_move Cmd) { if (Cmd.use_time == 0) return; SetServerPos(Cmd.dest); m_vMoveDir = Cmd.dest - GetPos(); float fDist = m_vMoveDir.Normalize(); m_bStopMove = false; // If destination position is too far to us, forcely pull player // to that position. if (fDist >= MAX_LAGDIST) { SetPos(Cmd.dest); m_pEPWorkMan.FinishWork(CECEPWork.EP_work_ID.WORK_MOVE); return; } int iMoveMode = Cmd.move_mode; m_cdr.bTraceGround = true; if (((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_AIR) != 0) { m_iMoveEnv = (int)MoveEnvironment.MOVEENV_AIR; m_cdr.bTraceGround = false; } else if (((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_WATER) != 0) { m_iMoveEnv = (int)MoveEnvironment.MOVEENV_WATER; m_cdr.bTraceGround = false; ShowWing(false); } else { m_iMoveEnv = (int)MoveEnvironment.MOVEENV_GROUND; if (!IsFlying()) ShowWing(false); } switch ((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_MASK) { case GPMoveMode.GP_MOVE_WALK: m_iMoveMode = (int)MoveMode.MOVE_MOVE; m_bWalkRun = false; break; case GPMoveMode.GP_MOVE_RUN: m_iMoveMode = (int)MoveMode.MOVE_MOVE; m_bWalkRun = true; break; case GPMoveMode.GP_MOVE_SLIDE: m_iMoveMode = (int)MoveMode.MOVE_SLIDE; break; case GPMoveMode.GP_MOVE_FALL: m_iMoveMode = (int)MoveMode.MOVE_FREEFALL; m_cdr.bTraceGround = false; break; case GPMoveMode.GP_MOVE_FLYFALL: m_cdr.bTraceGround = false; break; case GPMoveMode.GP_MOVE_JUMP: m_iMoveMode = (int)MoveMode.MOVE_JUMP; m_cdr.bTraceGround = false; break; default: return; } long dwTimeNow = Environment.TickCount; long dwDeltaTime = (dwTimeNow > m_dwLastMoveTime) ? (dwTimeNow - m_dwLastMoveTime) : 0; m_dwLastMoveTime = dwTimeNow; if (dwDeltaTime < 500) dwDeltaTime = 500; if (dwDeltaTime > 1000) dwDeltaTime = 1000; float fSpeed = (Cmd.sSpeed) / 256f; // short / 256 <=> FIX8TOFLOAT(short) m_fMoveSpeed = fDist / (dwDeltaTime * 0.001f); Mathf.Clamp(m_fMoveSpeed, 0.0f, fSpeed * 1.2f); A3DVECTOR3 vDir = m_vMoveDir; vDir.y = 0.0f; if (!vDir.IsZero()) { vDir.Normalize(); //StartModelMove(vDir, g_vAxisY, 150); //ChangeModelTargetDirAndUp(vDir, g_vAxisY); } if (m_pEPWorkMan.GetCurrentWorkType() < CECEPWorkMan.Work_type.WT_NORMAL || !m_pEPWorkMan.FindWork(CECEPWorkMan.Work_type.WT_NORMAL, CECEPWork.EP_work_ID.WORK_MOVE)) { m_pEPWorkMan.StartNormalWork(new CECEPWorkMove(m_pEPWorkMan)); } // Play action if (IsValidAction(m_iCurAction)) { if (!IsPlayingAction((int)PLAYER_ACTION_TYPE.ACT_TRICK_JUMP) && !IsPlayingAction((int)PLAYER_ACTION_TYPE.ACT_TRICK_RUN)) { if (m_iMoveMode == Move_Mode.MOVE_JUMP || m_iMoveMode == Move_Mode.MOVE_SLIDE) PlayAction((int)PLAYER_ACTION_TYPE.ACT_JUMP_LOOP, false, 200, false, true); else PlayAction(GetMoveStandAction(true), false, 200, false, true); } } else PlayAction(GetMoveStandAction(true), true, 200, false, true); } public bool MovingTo(float dwDeltaTime) { bool bRet = false; A3DVECTOR3 vPos, vCurPos = GetPos(); float fDeltaTime = dwDeltaTime; if (m_bStopMove) { A3DVECTOR3 vDir = m_vServerPos - vCurPos; float fDist = vDir.Normalize(); if (vDir.IsZero()) return false; Vector3 flatDir = EC_Utility.ToVector3(vDir); flatDir.y = 0; if (flatDir.sqrMagnitude > 0.001f) { Quaternion targetRotation = Quaternion.LookRotation(flatDir); if (Quaternion.Angle(transform.rotation, targetRotation) < 0.5f) transform.rotation = targetRotation; else transform.rotation = Quaternion.Slerp( transform.rotation, targetRotation, rotationSpeed * Time.deltaTime ); } vPos = MoveStep(vDir, m_fMoveSpeed, fDeltaTime); float fMoveDelta = A3d_Magnitude(vPos - vCurPos); if (Math.Abs(fMoveDelta - 0f) <= float.Epsilon || fMoveDelta >= fDist) //!fMoveDelta <=> (Math.Abs(fMoveDelta - 0f) <= float.Epsilon) Compare with 0 { SetPos(m_vServerPos); PlayAction(GetMoveStandAction(false), true, 200, false); bRet = true; } else { SetPos(vPos); } } else // Just move on { // If we have move so far from destination and still don't // receive new 'move' or 'stop move' command, it's better to // stop moving and goto last destination at once if (m_vMoveDir.IsZero()) return false; A3DVECTOR3 vDir = m_vMoveDir; vDir.Normalize(); Vector3 flatDir = EC_Utility.ToVector3(vDir); flatDir.y = 0; if (flatDir.sqrMagnitude > 0.001f) { Quaternion targetRotation = Quaternion.LookRotation(flatDir); if (Quaternion.Angle(transform.rotation, targetRotation) < 0.5f) transform.rotation = targetRotation; else transform.rotation = Quaternion.Slerp( transform.rotation, targetRotation, rotationSpeed * Time.deltaTime ); } vPos = MoveStep(vDir, m_fMoveSpeed, fDeltaTime); SetPos(vPos); float fDist = A3d_Magnitude(m_vServerPos - vCurPos); if (fDist >= MAX_LAGDIST) { SetPos(m_vServerPos); return true; } } return bRet; } public void StopMoveTo(cmd_object_stop_move Cmd) { m_vMoveDir = Cmd.dest - GetPos(); m_bStopMove = true; m_fMoveSpeed = (Cmd.sSpeed) / 256f; m_vStopDir = glb_DecompressDirH(Cmd.dir); SetServerPos(Cmd.dest); float fDist = m_vMoveDir.Normalize(); if (fDist >= MAX_LAGDIST || m_fMoveSpeed < 0.01f) { m_bStopMove = false; SetPos(Cmd.dest); PlayAction(GetMoveStandAction(true), true, 200, false); m_pEPWorkMan.FinishWork(CECEPWork.EP_work_ID.WORK_MOVE); return; } int iMoveMode = Cmd.move_mode; m_cdr.bTraceGround = true; if (((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_AIR) != 0) { m_iMoveEnv = (int)MoveEnvironment.MOVEENV_AIR; m_cdr.bTraceGround = false; } else if (((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_WATER) != 0) { m_iMoveEnv = (int)MoveEnvironment.MOVEENV_WATER; m_cdr.bTraceGround = false; } else m_iMoveEnv = (int)MoveEnvironment.MOVEENV_GROUND; switch ((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_MASK) { case GPMoveMode.GP_MOVE_WALK: m_iMoveMode = (int)MoveMode.MOVE_MOVE; m_bWalkRun = false; break; case GPMoveMode.GP_MOVE_RUN: m_iMoveMode = (int)MoveMode.MOVE_MOVE; m_bWalkRun = true; break; case GPMoveMode.GP_MOVE_SLIDE: m_iMoveMode = (int)MoveMode.MOVE_SLIDE; break; case GPMoveMode.GP_MOVE_FALL: m_iMoveMode = (int)MoveMode.MOVE_FREEFALL; m_cdr.bTraceGround = false; break; case GPMoveMode.GP_MOVE_FLYFALL: m_cdr.bTraceGround = false; break; case GPMoveMode.GP_MOVE_JUMP: m_iMoveMode = (int)MoveMode.MOVE_JUMP; m_cdr.bTraceGround = false; break; default: return; } A3DVECTOR3 vDir = m_vMoveDir; vDir.y = 0.0f; if (!vDir.IsZero()) { vDir.Normalize(); //StartModelMove(vDir, g_vAxisY, 150); //ChangeModelTargetDirAndUp(vDir, g_vAxisY); } if (m_pEPWorkMan.GetCurrentWorkType() < CECEPWorkMan.Work_type.WT_NORMAL || !m_pEPWorkMan.FindWork(CECEPWorkMan.Work_type.WT_NORMAL, CECEPWork.EP_work_ID.WORK_MOVE)) { m_pEPWorkMan.StartNormalWork(new CECEPWorkMove(m_pEPWorkMan)); } PlayAction(GetMoveStandAction(true), true, 200, false); } public float GetDistToHost() { return m_fDistToHost; } // Decompress horizontal direction A3DVECTOR3 glb_DecompressDirH(byte byDir) { float fInter = 360.0f / 256.0f; float fRad = Mathf.Deg2Rad * (byDir * fInter); A3DVECTOR3 v; v.x = (float)Math.Cos(fRad); v.z = (float)Math.Sin(fRad); v.y = 0.0f; return v; } public void EnterFightState() { m_FightCnt.Reset(false); } private A3DVECTOR3 MoveStep(A3DVECTOR3 vDir, float fSpeed, float fTime) { A3DVECTOR3 vRealDir = vDir; // OnAirMove only accept positive speed value if (fSpeed < 0.0f) { vRealDir = -vDir; fSpeed = -fSpeed; } m_cdr.vCenter = m_aabbServer.Center; m_cdr.vVelocity = vRealDir * fSpeed; m_cdr.t = fTime; m_cdr.bTestTrnOnly = false; //OtherPlayerMove(m_cdr); A3DVECTOR3 vDelta = m_cdr.t * m_cdr.vVelocity; m_cdr.vCenter += vDelta; m_cdr.vecGroundNormal = EC_Utility.ToA3DVECTOR3(g_vAxisY); if (m_cdr.bTraceGround) SetGroundNormal(m_cdr.vecGroundNormal); else SetGroundNormal(EC_Utility.ToA3DVECTOR3(g_vAxisY)); return m_cdr.vCenter - EC_Utility.ToA3DVECTOR3(g_vAxisY) * m_cdr.vExts.y; } void OtherPlayerMove(OtherPlayer_Move_Info OPMoveInfo) { A3DVECTOR3 vDelta = OPMoveInfo.t * OPMoveInfo.vVelocity; OPMoveInfo.vCenter += vDelta; OPMoveInfo.vecGroundNormal = EC_Utility.ToA3DVECTOR3(g_vAxisY); A3DVECTOR3 vGroundPos, vNormal; // Now, we directly interpolate the pos, and we don't use bTraceGround //if (OPMoveInfo.bTestTrnOnly) //{ // GetTerrainInfo(OPMoveInfo.vCenter, vGroundPos, vNormal); // vGroundPos.y += OPMoveInfo.vExts.y; // if (OPMoveInfo.vCenter.y < vGroundPos.y + 0.1f) // OPMoveInfo.vecGroundNormal = vNormal; // // verify not below the terrain // if (OPMoveInfo.vCenter.y < vGroundPos.y) // OPMoveInfo.vCenter.y = vGroundPos.y; //} //else //{ // if (VertRayTrace(OPMoveInfo.vCenter, vGroundPos, vNormal, 3.0f)) // { // OPMoveInfo.vecGroundNormal = vNormal; // vGroundPos.y += OPMoveInfo.vExts.y; // // verify not below the ground // if (OPMoveInfo.vCenter.y < vGroundPos.y) // OPMoveInfo.vCenter.y = vGroundPos.y; // } //} } private float A3d_Magnitude(A3DVECTOR3 v) { return Mathf.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z); } // Set server position public void SetServerPos(A3DVECTOR3 vPos) { m_vServerPos = vPos; // If this player is a mule, change it's rider's server pos too. if (m_iBuddyId != 0 && !m_bHangerOn) { var pPlayer = EC_ManMessageMono.Instance.GetECManPlayer.GetElsePlayer(m_iBuddyId); if (pPlayer) pPlayer.SetServerPos(vPos); } } protected override void Update() { base.Update(); //bool bSelected = pHost.GetSelectedTarget() == GetPlayerInfo().cid; //if (!m_pPlayerModel && !m_bLoadingModel && IsBaseInfoReady() && IsCustomDataReady() && IsEquipDataReady()) //{ // if ((!m_bUseHintModel && m_iBoothState != 2) || bSelected) // { // // prepared equip info // //memcpy(m_aEquips, m_aNewEquips, sizeof(m_aEquips)); // if (ShouldUseModel()) // LoadPlayerSkeleton(false); // m_bLoadingModel = true; // } //} MovingTo(Time.deltaTime); if (pHost != null /*&& pHost.IsSkeletonReady()*/) { m_fDistToHost = CalcDist(pHost.GetPos(), true); m_fDistToHostH = CalcDist(pHost.GetPos(), false); if (m_pPlayerModel != null) { if (m_fDistToHostH < EC_Game.GetSettingViewDistanceEP().fShow) { m_pPlayerModel.SetActive(true); } else if (m_fDistToHostH > EC_Game.GetSettingViewDistanceEP().fHide) { m_pPlayerModel.SetActive(false); } } } m_pEPWorkMan?.Tick(Time.deltaTime); } public void SetPos(A3DVECTOR3 vPos) { Vector3 vector = new Vector3(); vector.x = vPos.x; vector.y = vPos.y; vector.z = vPos.z; transform.position = vector; m_aabb.Center = vPos + new A3DVECTOR3(0.0f, m_aabb.Extents.y, 0.0f); m_aabb.CompleteMinsMaxs(); m_aabbServer.Center = vPos + new A3DVECTOR3(0.0f, m_aabbServer.Extents.y, 0.0f); m_aabbServer.CompleteMinsMaxs(); } public A3DVECTOR3 GetPos() { A3DVECTOR3 result = new A3DVECTOR3(); result.x = transform.position.x; result.y = transform.position.y; result.z = transform.position.z; return result; } // Get player's real position on server public A3DVECTOR3 GetServerPos() { return m_vServerPos; } public bool ProcessMessage(ECMSG Msg) { if(Msg.dwMsg == EC_MsgDef.MSG_PM_PLAYEREXTSTATE) { Debug.Log("MSG_PM_ElsePLAYEREXTSTATE"); } switch (Msg.dwMsg) { case EC_MsgDef.MSG_PM_PLAYERFLY: OnMsgPlayerFly(Msg); break; case EC_MsgDef.MSG_PM_PLAYERBASEINFO: OnMsgPlayerBaseInfo(Msg); break; case EC_MsgDef.MSG_PM_PLAYEREXTSTATE: OnMsgPlayerExtState(Msg); break; case EC_MsgDef.MSG_PM_PLAYEREQUIPDATA: OnMsgPlayerEquipData(Msg); break; case EC_MsgDef.MSG_PM_PLAYERATKRESULT: OnMsgPlayerAtkResult(Msg); break; case EC_MsgDef.MSG_PM_CASTSKILL: OnMsgPlayerCastSkill(Msg); break; case EC_MsgDef.MSG_PM_ENCHANTRESULT: OnMsgEnchantResult(Msg); break; case EC_MsgDef.MSG_PM_PLAYERDOEMOTE: OnMsgPlayerDoEmote(Msg); break; case EC_MsgDef.MSG_PM_PLAYERSKILLRESULT: OnMsgPlayerSkillResult(Msg); break; case EC_MsgDef.MSG_PM_PLAYERGATHER: OnMsgPlayerGather(Msg); break; case EC_MsgDef.MSG_PM_PLAYERMOUNT: OnMsgPlayerMount(Msg); break; } return true; } /// /// Handles skill attack result messages for else players. /// /// This method is called when the server sends MSG_PM_PLAYERSKILLRESULT (OBJECT_SKILL_ATTACK_RESULT). /// Unlike OnMsgPlayerAtkResult which handles melee attacks, this handles skill attacks specifically. /// /// Flow: /// 1. Parse cmd_object_skill_attack_result (includes skill_id directly) /// 2. Face target /// 3. Call PlayAttackEffect with skill_id from command (triggers GFX system) /// 4. Enter fight state if skill is attack or curse type /// /// C++ equivalent: CECElsePlayer::OnMsgPlayerSkillResult (EC_ElsePlayer.cpp:1787) /// private void OnMsgPlayerSkillResult(ECMSG Msg) { cmd_object_skill_attack_result pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); // Face to target TurnFaceTo(pCmd.target_id); // Call PlayAttackEffect with skill_id directly from command (like C++ does) // Unlike OnMsgPlayerAtkResult, we get skill_id from the command structure, not from m_pCurSkill int attackTime = int.MinValue; PlayAttackEffect(pCmd.target_id, pCmd.skill_id, 0, -1, (uint)pCmd.attack_flag, pCmd.speed * 50, ref attackTime, pCmd.section); // Check skill type and enter fight state if needed (matching C++ logic) byte skillType = ElementSkill.GetType((uint)pCmd.skill_id); if (skillType == (byte)skill_type.TYPE_ATTACK || skillType == (byte)skill_type.TYPE_CURSE) { EnterFightState(); } } public void HandleRevive(short sReviveType, A3DVECTOR3 pos) { SetServerPos(pos); SetPos(pos); m_pEPWorkMan?.FinishWork(CECEPWork.EP_work_ID.WORK_DEAD); m_pEPWorkMan?.StartNormalWork(new CECEPWorkIdle(m_pEPWorkMan, CECEPWork.Idle_work_type.IDLE_REVIVE, 0, 0)); } async void OnMsgPlayerBaseInfo(ECMSG Msg) { playerbaseinfo_re p = (playerbaseinfo_re)Msg.dwParam1; GRoleBase roleBase = p.Player; // if (p.Roleid != m_PlayerInfo.cid) // { // BMLogger.LogError($"OnMsgPlayerBaseInfo:: Expected {m_PlayerInfo.cid}, but got {p.Roleid}"); // return; // } if (roleBase.name.Length <= 0) return; string strName = Encoding.Unicode.GetString(roleBase.name.ByteArray); SetPlayerBriefInfo(roleBase.cls, roleBase.gender, strName); EventBus.Publish(new CECHostPlayer.NPCINFO(strName, 100,100, p.Roleid)); BMLogger.Log($"SetPlayerModel: {roleBase.cls} {roleBase.gender}"); await SetPlayerModel((byte)roleBase.cls, (byte)roleBase.gender); if (TryGetComponent(out var visual)) { visual.InitPlayerEventDoneHandler(); } } public async void OnMsgPlayerEquipData(ECMSG Msg) { // using namespace S2C; // int crc, *aAdded=NULL; int crc = -1; int[] aAdded = null; bool bReset = false; long iAddMask = 0; long iDelMask = 0; int dwParam2 = Convert.ToInt32(Msg.dwParam2); if (dwParam2 == CommandID.EQUIP_DATA) { cmd_equip_data pCmd = cmd_equip_data.FromBytes((byte[])Msg.dwParam1); bReset = true; crc = pCmd.crc; iAddMask = pCmd.mask; iDelMask = 0; aAdded = pCmd.data; } else // Msg.dwParam2 == EQUIP_DATA_CHANGED { cmd_equip_data_changed pCmd = cmd_equip_data_changed.FromBytes((byte[])Msg.dwParam1); bReset = false; crc = pCmd.crc; iAddMask = pCmd.mask_add; iDelMask = pCmd.mask_del; aAdded = pCmd.data_add; } // // Change equipment // ChangeEquipments(bReset, crc, iAddMask, iDelMask, aAdded); await QueueChangeEquipments(bReset, crc, iAddMask, iDelMask, aAdded); } private async UniTask QueueChangeEquipments(bool bReset, int crc, long iAddMask, long iDelMask, int[] aAddedEquip) { while (!IsPlayerModelReady) { await UniTask.DelayFrame(1); } ChangeEquipments(bReset, crc, iAddMask, iDelMask, aAddedEquip); } /// /// Handles attack result messages for else players (both melee and skill attacks). /// /// For skill attacks: /// - Uses m_pCurSkill (set in OnMsgPlayerCastSkill) to get skill ID /// - Calls PlayAttackEffect with skill ID, which triggers GFX system /// - GFX system (A3DSkillGfxComposerMan) spawns effects at hook positions /// /// For melee attacks: /// - idSkill is 0, triggers normal melee attack work /// void OnMsgPlayerAtkResult(ECMSG Msg) { cmd_object_atk_result pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); //ASSERT(pCmd && pCmd.attacker_id == m_PlayerInfo.cid); // Face to target TurnFaceTo(pCmd.target_id); // TO DO: fix later int attackTime = int.MinValue; PlayAttackEffect(pCmd.target_id, 0, 0, -1, (uint)pCmd.attack_flag, pCmd.speed * 50, ref attackTime); BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] PlayAttackEffect: Complete, attackTime={attackTime}"); if (!m_pEPWorkMan.FindWork(CECEPWorkMan.Work_type.WT_NORMAL, CECEPWork.EP_work_ID.WORK_HACKOBJECT)) { m_pEPWorkMan.StartNormalWork(new CECEPWorkMelee(m_pEPWorkMan, pCmd.target_id)); } // Enter fight state EnterFightState(); } /// /// Handles skill casting messages from server for else players. /// /// Flow: /// 1. Server sends OBJECT_CAST_SKILL . This handler plays cast animation /// 2. Server sends SKILL_PERFORM . Skill execution begins (for durative skills) /// 3. Server sends attack result . OnMsgPlayerAtkResult triggers PlayAttackEffect /// 4. PlayAttackEffect . CECAttacksMan.AddSkillAttack . GFX system spawns effects /// 5. Server sends SKILL_INTERRUPTED . Clears casting state (if interrupted) /// /// Note: Else players don't maintain skill lists, so we create temporary CECSkill objects /// for tracking purposes only. The actual skill data comes from ElementSkill static methods. /// private void OnMsgPlayerCastSkill(ECMSG Msg) { int commandID = Convert.ToInt32(Msg.dwParam2); switch (commandID) { case CommandID.OBJECT_CAST_SKILL: { cmd_object_cast_skill pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); // Get skill object (else players don't have skill lists, so we create a temporary skill reference) // For else players, we mainly need the skill ID for animation purposes int skillID = pCmd.skill; if ((m_pCurSkill = new CECSkill(pCmd.skill, pCmd.level)) == null) { BMLogger.LogError($"OnMsgPlayerCastSkill: Failed to create CECSkill for skillID={pCmd.skill}, level={pCmd.level}"); return; } // Store current skill target m_idCurSkillTarget = pCmd.target; uint dwPeriod = (uint)(pCmd.time + m_pCurSkill.GetExecuteTime()) * 10; m_pEPWorkMan.StartNormalWork(new CECEPWorkSpell(m_pEPWorkMan, dwPeriod, new CECSkill(pCmd.skill, pCmd.level), pCmd.target)); PlaySkillCastAction(m_pCurSkill.GetSkillID()); // // Face the target // TurnFaceTo(pCmd.target); // // Play skill cast animation // PlaySkillCastAction(skillID); // // Create a temporary skill object for tracking (if needed) // // Note: Else players don't maintain skill lists like host player does // // We create a minimal skill object just for the current cast // if (m_pCurSkill == null || m_pCurSkill.GetSkillID() != skillID) // { // // Create a temporary skill object with level 1 (we don't know the actual level) // m_pCurSkill = new CECSkill(skillID, 1); // } // // Enter fight state // EnterFightState(); break; } case CommandID.OBJECT_CAST_INSTANT_SKILL: { StopCurPosWork(); ClearCastingSkill(); m_pEPWorkMan.FinishWork(CECEPWork.EP_work_ID.WORK_SPELL); cmd_object_cast_instant_skill pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); PlaySkillCastAction(pCmd.skill); break; } case CommandID.OBJECT_CAST_POS_SKILL: { StopCurPosWork(); ClearCastingSkill(); m_pEPWorkMan.FinishWork(CECEPWork.EP_work_ID.WORK_SPELL); cmd_object_cast_pos_skill pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); SetServerPos(pCmd.pos); float fDist = A3d_Magnitude(pCmd.pos - GetPos()); if (fDist <= 0.0001f) { SetPos(pCmd.pos); return; } int nExecuteTime = ElementSkill.GetExecuteTime((uint)pCmd.skill, pCmd.level); nExecuteTime = Mathf.Max(nExecuteTime, 50); m_fMoveSpeed = 1.3f * fDist * 1000.0f / nExecuteTime; m_pEPWorkMan.StartNormalWork(new CECEPWorkFlashMove(m_pEPWorkMan, pCmd.pos, m_fMoveSpeed)); break; } case CommandID.SKILL_INTERRUPTED: { // Skill was interrupted, clear current skill cmd_skill_interrupted pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); ClearComActFlagAllRankNodes(false); ClearCastingSkill(); m_pEPWorkMan.FinishWork(CECEPWork.EP_work_ID.WORK_SPELL); StopSkillCastAction(); break; } default: { break; } } } private void ClearCastingSkill(){ if (m_pCurSkill != null){ m_pCurSkill = null; } m_idCurSkillTarget = 0; } private void StopCurPosWork() { if (m_pEPWorkMan.IsWorkRunning(CECEPWork.EP_work_ID.WORK_MOVE)){ m_pEPWorkMan.FinishRunningWork(CECEPWork.EP_work_ID.WORK_MOVE); } if (m_pEPWorkMan.IsWorkRunning(CECEPWork.EP_work_ID.WORK_PASSIVEMOVE)){ m_pEPWorkMan.FinishRunningWork(CECEPWork.EP_work_ID.WORK_PASSIVEMOVE); } if (m_pEPWorkMan.IsWorkRunning(CECEPWork.EP_work_ID.WORK_FLASHMOVE)){ m_pEPWorkMan.FinishRunningWork(CECEPWork.EP_work_ID.WORK_FLASHMOVE); } } private async void LoadAppearGfx() { if (!m_pAppearGFX && m_iAppearFlag == (int)PlayerAppearFlag.APPEAR_ENTERWORLD) { CECGFXCaster pGfxCaster = EC_Game.GetGFXCaster(); m_pAppearGFX = await pGfxCaster.LoadGFXEx(EC_Resource.res_GFXFile((int)GfxResourceType.RES_GFX_PLAYERAPPEAR)); var pos = GetPos(); m_pAppearGFX.transform.position = new Vector3(pos.x, pos.y, pos.z); m_pAppearGFX.transform.parent = transform; m_pAppearGFX.Play(); } } // Play an attack effect //void PlayAttackEffect(int idTarget, int idSkill, int skillLevel, int nDamage, // uint dwModifier, int nAttackSpeed, int[] piAttackTime/* NULL */, int nSection) //{ // if (!IsAllResReady()) // return; // if (idSkill == 0) // { // int idWeapon = IsShapeChanged() ? 0 : GetWeaponID(); // int nTimeFly = 10; // if (idWeapon != 0) // { // // ¿´¿´ÊDz»ÊÇÔ¶³ÌÎäÆ÷ // DATA_TYPE dt; // WEAPON_ESSENCE pWeapon = (WEAPON_ESSENCE)g_pGame.GetElementDataMan().get_data_ptr(idWeapon, ID_SPACE_ESSENCE, dt); // //ASSERT(dt == DT_WEAPON_ESSENCE); // if (dt == DT_WEAPON_ESSENCE && pWeapon && pWeapon.require_projectile) // { // nTimeFly = 700; // if (m_aEquips[EQUIPIVTR_PROJECTILE]) // idWeapon = m_aEquips[EQUIPIVTR_PROJECTILE]; // set weapon to the projectile // } // } // if (g_pGame.GetGameRun().GetWorld().GetAttacksMan().FindAttackByAttacker(GetPlayerInfo().cid)) // { // // signal early attack event // ClearComActFlagAllRankNodes(true); // } // // melee attack // CECAttackEvent pAttack = g_pGame.GetGameRun().GetWorld().GetAttacksMan().AddMeleeAttack( // GetPlayerInfo().cid, idTarget, idWeapon, dwModifier, nDamage, nTimeFly); // if (pAttack != null) // { // if (!IsDead() && (dwModifier & CECAttackEvent::MOD_RETORT) == 0 // && (dwModifier & CECAttackEvent::MOD_ATTACK_AURA) == 0 // && PlayAttackAction(nAttackSpeed, piAttackTime, &pAttack.m_bSignaled) // && (dwModifier & CECAttackEvent::MOD_BEAT_BACK) == 0) // { // } // else // { // pAttack.m_bSignaled = true; // } // } // } // else // { // if (skillLevel == 0) // { // if (m_pCurSkill) // skillLevel = m_pCurSkill.GetSkillLevel(); // else // skillLevel = 1; // } // CECAttackEvent pAttack = null; // // first try to find if there is already a skill attack event in attackman // CECAttackerEvents attackerEvents = g_pGame.GetGameRun().GetWorld().GetAttacksMan().FindAttackByAttacker(GetPlayerInfo().cid); // if (attackerEvents) // { // CECAttackEvent pAttack = attackerEvents.Find(idSkill, nSection); // if (() != null) // { // // Ãæ¹¥»÷µÄ·ÇµÚÒ»´ÎÉ˺¦ÏûÏ¢ // pAttack.AddTarget(idTarget, dwModifier, nDamage); // goto EXIT; // } // else // { // attackerEvents.Signal(); // } // } // if (GNET::ElementSkill::IsGoblinSkill(idSkill) && // GNET::ElementSkill::GetType(idSkill) == 2) // { // pAttack = g_pGame.GetGameRun().GetWorld().GetAttacksMan().AddSkillAttack( // GetPlayerInfo().cid, GetPlayerInfo().cid, idTarget, GetWeaponID(), idSkill, skillLevel, dwModifier, nDamage); // } // else // { // // begin a skill attack // pAttack = g_pGame.GetGameRun().GetWorld().GetAttacksMan().AddSkillAttack( // GetPlayerInfo().cid, m_idCurSkillTarget, idTarget, GetWeaponID(), idSkill, skillLevel, dwModifier, nDamage); // } // if (pAttack) // { // pAttack.SetSkillSection(nSection); // if (!IsDead() && (dwModifier & CECAttackEvent::MOD_RETORT) == 0 // && (dwModifier & CECAttackEvent::MOD_ATTACK_AURA) == 0 // && PlaySkillAttackAction(idSkill, nAttackSpeed, NULL, nSection, &pAttack.m_bSignaled) // && (dwModifier & CECAttackEvent::MOD_BEAT_BACK) == 0) // { // } // else // { // pAttack.m_bSignaled = true; // } // } // EXIT: // // For skill attacking, time is always set to 0 // if (piAttackTime) // *piAttackTime = 0; // } //} //public A3DVECTOR3 GetPos() //{ // return new A3DVECTOR3(transform.position.x, transform.position.y, transform.position.z); //} // Change equipment bool ChangeEquipments(bool bReset, int crc, long iAddMask, long iDelMask, int[] aAddedEquip) { m_PlayerInfo.crc_e = crc; if (bReset) { // m_i64NewEqpMask = EQUIP_MASK64_ALL; m_bEquipReady = true; // memset(m_aEquips, 0xff, sizeof (m_aEquips)); // memset(m_aNewEquips, 0, sizeof (m_aNewEquips)); } // Remove equipment at first if (iDelMask != 0) { for (int i=0; i < InventoryConst.SIZE_ALL_EQUIPIVTR; i++) { if ((iDelMask & (1L << i)) != 0) { m_aNewEquips[i] = 0; m_i64NewEqpMask |= (1L << i); } } } if (iAddMask != 0 && aAddedEquip != null) { int iCount = 0; for (int i=0; i < InventoryConst.SIZE_ALL_EQUIPIVTR; i++) { if ((iAddMask & (1L << i)) != 0) { m_aNewEquips[i] = aAddedEquip[iCount++]; m_i64NewEqpMask |= (1L << i); } } } // this is some sort of hack to get the equipment to load immediately // TODO: Check again if this should be done here ShowEquipments(m_aNewEquips, true, true); // TODO: Implement the rest of the equipment logic /*/ if (!ShouldUseModel()) return true; if (IsSkeletonReady()) LoadPlayerEquipments(); // Deal with goblin£¨¼òµ¥Ä£ÐͲ»¼ÓÔØÐ¡¾«Á飩 if(m_aNewEquips[EQUIPIVTR_GOBLIN] != 0 && ShouldUseClothedModel()) { int tid = m_aNewEquips[EQUIPIVTR_GOBLIN] & 0x0000ffff; int idModel = (m_aNewEquips[EQUIPIVTR_GOBLIN] >>16) & 0x000000ff; int iRefineLvl = (m_aNewEquips[EQUIPIVTR_GOBLIN] >>24) & 0x000000ff; ASSERT(idModel >= 1 && idModel <= 4); ASSERT(iRefineLvl >=0 && iRefineLvl <= 36); if( !m_pGoblin ) { m_pGoblin = new CECGoblin(); if(!m_pGoblin.Init(tid, idModel,iRefineLvl,this)) { ASSERT(0); m_pGoblin.Release(); delete m_pGoblin; m_pGoblin = NULL; } } else { if(tid != m_pGoblin.GetTemplateID() || idModel != (int)m_pGoblin.GetModelID() || iRefineLvl != m_pGoblin.GetRefineLevel()) { m_pGoblin.Release(); delete m_pGoblin; m_pGoblin = NULL; m_pGoblin = new CECGoblin(); if(!m_pGoblin.Init(tid, idModel,iRefineLvl,this)) { ASSERT(0); m_pGoblin.Release(); delete m_pGoblin; m_pGoblin = NULL; } } } } else // m_aNewEquips[EQUIPIVTR_GOBLIN] == 0 { if(m_pGoblin) { m_pGoblin.Release(); delete m_pGoblin; m_pGoblin = NULL; } } // Deal with armet if(InFashionMode()) { UpdateHairModel(true, m_aNewEquips[EQUIPIVTR_FASHION_HEAD]); } else { UpdateHairModel(true, m_aNewEquips[EQUIPIVTR_HEAD]); } //*/ return true; } private void SetPlayerBriefInfo(int iProf, int iGender, string szName) { m_iProfession = iProf; m_iGender = iGender; m_bBaseInfoReady = true; SetPlayerName(szName ?? ""); EC_Game.GetGameRun().AddPlayerName(m_PlayerInfo.cid, szName, true); GetComponentInChildren().RefreshName(); } // Level up public void LevelUp() { // if (m_pLevelUpGFX) // m_pLevelUpGFX.Start(true); // PlayGfx(EC_Resource.res_GFXFile((int)GfxResourceType.RES_GFX_LEVELUP), null, 1f,1);//PLAYERMODEL_TYPEALL } bool IsPlayingAction(int iAction) { return m_iCurAction == iAction; } private void OnMsgPlayerDoEmote(ECMSG Msg) { 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).Forget(); // if( m_iBuddyId ) // { // CECPlayer pBuddy = m_pPlayerMan.GetPlayer(m_iBuddyId); // if (pBuddy) // pBuddy.DoEmote(pCmd.emotion); // } } else if (cmd == CommandID.OBJECT_EMOTE_RESTORE) { PlayAction((int)PLAYER_ACTION_TYPE.ACT_STAND, false); } } // Do emote action private async UniTask DoEmote(int idEmote) { 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; } PlayAction(iAction); if (!bSession){} { await UniTask.Delay(3000); PlayAction(GetMoveStandAction(false, false), true, 300, true); } return true; } void OnMsgPlayerGather(ECMSG Msg) { int cmd = Convert.ToInt32(Msg.dwParam2); if (cmd == CommandID.PLAYER_GATHER_START) { cmd_player_gather_start pCmd =GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); EC_ManMatter pMatterMan = EC_ManMessageMono.Instance.GetECManMatter;//g_pGame.GetGameRun().GetWorld().GetMatterMan(); CECMatter pMatter = pMatterMan.GetMatter(pCmd.mid); // if (pMatter && pMatter.IsMonsterSpiritMine()) { // StartMonsterSpiritConnectGfx(pCmd.mid, pMatter.GetPos()); // m_pEPWorkMan.StartNormalWork(new CECEPWorkPickUp(m_pEPWorkMan, pCmd.use_time * 1000, CECEPWorkPickUp::GATHER_MONSTER_SPIRIT)); // } else { m_pEPWorkMan.StartNormalWork(new CECEPWorkPickUp(m_pEPWorkMan, 0, CECEPWorkPickUp.PickUpType.GATHER_ITEM, pMatter ? pMatter.GetTemplateID() : 0)); // } } else if (cmd == CommandID.PLAYER_GATHER_STOP) { cmd_player_gather_stop pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); m_pEPWorkMan.FinishWork(CECEPWork.EP_work_ID.WORK_PICKUP); // m_pEPWorkMan.FinishWork(CECEPWork::WORK_PICKUP); // StopMonsterSpiritConnectGfx(); } else if (cmd == CommandID.MINE_GATHERED) { cmd_mine_gathered pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); // ASSERT(pCmd && pCmd.player_id == m_PlayerInfo.cid); // StartMonsterSpiritBallGfx(); } } // Is base info ready ? bool IsBaseInfoReady() { return m_bBaseInfoReady; } // Is customized data ready ? bool IsCustomDataReady() { return m_bCustomReady; } // Is equipment data ready ? public bool IsEquipDataReady() { return m_bEquipReady; } void OnMsgPlayerFly(ECMSG Msg) { if (IsAllResReady()) { if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_LANDING) ShowWing(false); else // OBJECT_TAKEOFF ShowWing(true); } if (Convert.ToInt32(Msg.dwParam2) == CommandID.OBJECT_LANDING) m_dwStates &= ~(uint)PlayerNPCState.GP_STATE_FLY; else m_dwStates |= (uint)PlayerNPCState.GP_STATE_FLY; } // Load player equipments bool LoadPlayerEquipments() { ShowEquipments(m_aNewEquips, false, false); SetResReadyFlag((uint)PlayerResourcesReadyFlag.RESFG_SKIN, true); if (m_pPlayerModel) { // If weapon changed, action index may be changed, so update here int iCurAction = GetLowerBodyAction(); if (IsValidAction(iCurAction)) PlayAction(iCurAction, false); } return true; } public int GetEquipment(int index) { return m_aEquips[index]; } } // Player appear flag public enum PlayerAppearFlag { APPEAR_DISAPPEAR = -1, // Player disappear APPEAR_ENTERWORLD = 0, // Player join world APPEAR_RUNINTOVIEW, // Player run into view APPEAR_GHOST, // Player is in ghost state, in player list but not active }; }