Files
test/Assets/PerfectWorld/Scripts/Players/EC_ElsePlayer.cs
T
2026-03-13 16:03:47 +07:00

1289 lines
57 KiB
C#

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;
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
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::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;
}
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);
//if (!m_pPlayerModel) return;
//if (!IsValidAction(iCurAction)) return;
// PlayAction(GetMoveStandAction(true), true, 1, false);
// 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);
else
PlayAction(GetMoveStandAction(true), false);
}
}
else
PlayAction(GetMoveStandAction(true), true, 1, false);
}
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, 1, 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, 1, false);
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;
}
PlayAction(GetMoveStandAction(true), true, 1, 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);
}
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)
{
// Log ALL messages for this player to debug missing attack results
// Filter out common noise messages but log important ones
int msgType = (int)Msg.dwMsg;
int param2 = Convert.ToInt32(Msg.dwParam2);
// Log skill-related messages
if (msgType == EC_MsgDef.MSG_PM_CASTSKILL || msgType == EC_MsgDef.MSG_PM_PLAYERATKRESULT)
{
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] ProcessMessage: Received message, playerID={m_PlayerInfo.cid}, " +
$"msgType={msgType}, subID={Msg.iSubID}, param2={param2}, " +
$"m_pCurSkill={(m_pCurSkill != null ? m_pCurSkill.GetSkillID().ToString() : "null")}");
}
// Log any unknown command IDs in MSG_PM_CASTSKILL to catch OBJECT_SKILL_ATTACK_RESULT
if (msgType == EC_MsgDef.MSG_PM_CASTSKILL)
{
// Check if this might be OBJECT_SKILL_ATTACK_RESULT (unknown command ID)
if (param2 != CommandID.OBJECT_CAST_SKILL &&
param2 != CommandID.OBJECT_CAST_INSTANT_SKILL &&
param2 != CommandID.OBJECT_CAST_POS_SKILL &&
param2 != CommandID.SKILL_PERFORM &&
param2 != CommandID.SKILL_INTERRUPTED &&
param2 != CommandID.PLAYER_CAST_RUNE_SKILL &&
param2 != CommandID.PLAYER_CAST_RUNE_INSTANT_SKILL)
{
BMLogger.LogWarning($"[ELSEPLAYER_SKILL_FLOW] ProcessMessage: Unknown commandID={param2} in MSG_PM_CASTSKILL! " +
$"This might be OBJECT_SKILL_ATTACK_RESULT - we need to handle it!");
}
}
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_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_PLAYERDOEMOTE: OnMsgPlayerDoEmote(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERSKILLRESULT: OnMsgPlayerSkillResult(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERGATHER: OnMsgPlayerGather(Msg); break;
case EC_MsgDef.MSG_PM_PLAYERMOUNT: OnMsgPlayerMount(Msg); break;
}
return true;
}
/// <summary>
/// Handles skill attack result messages for else players.
///
/// This method is called when the server sends MSG_PM_PLAYERSKILLRESULT (OBJECT_SKILL_ATTACK_RESULT).
/// Unlike OnMsgPlayerAtkResult which handles melee attacks, this handles skill attacks specifically.
///
/// Flow:
/// 1. Parse cmd_object_skill_attack_result (includes skill_id directly)
/// 2. Face target
/// 3. Call PlayAttackEffect with skill_id from command (triggers GFX system)
/// 4. Enter fight state if skill is attack or curse type
///
/// C++ equivalent: CECElsePlayer::OnMsgPlayerSkillResult (EC_ElsePlayer.cpp:1787)
/// </summary>
private void OnMsgPlayerSkillResult(ECMSG Msg)
{
cmd_object_skill_attack_result pCmd = GPDataTypeHelper.FromBytes<cmd_object_skill_attack_result>((byte[])Msg.dwParam1);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerSkillResult: Entry, attackerID={pCmd.attacker_id}, targetID={pCmd.target_id}, " +
$"skillID={pCmd.skill_id}, damage={pCmd.damage}, speed={pCmd.speed}, attack_flag={pCmd.attack_flag}, section={pCmd.section}");
// 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;
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerSkillResult: Calling PlayAttackEffect: target={pCmd.target_id}, skillID={pCmd.skill_id}, " +
$"skillLevel=0, damage={pCmd.damage}, attack_flag={pCmd.attack_flag}, speed={pCmd.speed * 50}, section={pCmd.section}");
PlayAttackEffect(pCmd.target_id, pCmd.skill_id, 0, -1,
(uint)pCmd.attack_flag, pCmd.speed * 50, ref attackTime, pCmd.section);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerSkillResult: PlayAttackEffect complete, attackTime={attackTime}");
// 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();
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerSkillResult: Entered fight state (skillType={skillType})");
}
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerSkillResult: Complete");
}
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<PlayerVisual>(out var visual))
{
visual.InitPlayerEventDoneHandler();
}
}
public 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);
}
/// <summary>
/// Handles attack result messages for else players (both melee and skill attacks).
///
/// For skill attacks:
/// - Uses m_pCurSkill (set in OnMsgPlayerCastSkill) to get skill ID
/// - Calls PlayAttackEffect with skill ID, which triggers GFX system
/// - GFX system (A3DSkillGfxComposerMan) spawns effects at hook positions
///
/// For melee attacks:
/// - idSkill is 0, triggers normal melee attack work
/// </summary>
void OnMsgPlayerAtkResult(ECMSG Msg)
{
cmd_object_atk_result pCmd = GPDataTypeHelper.FromBytes<cmd_object_atk_result>((byte[])Msg.dwParam1);
//ASSERT(pCmd && pCmd.attacker_id == m_PlayerInfo.cid);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerAtkResult: Entry, attackerID={pCmd.attacker_id}, targetID={pCmd.target_id}, " +
$"damage={pCmd.damage}, speed={pCmd.speed}, attack_flag={pCmd.attack_flag}, m_pCurSkill={(m_pCurSkill != null ? m_pCurSkill.GetSkillID().ToString() : "null")}");
// Face to target
TurnFaceTo(pCmd.target_id);
// Check if this is a skill attack or melee attack
// For skill attacks, we need to get the skill ID from the current skill
int idSkill = 0;
int skillLevel = 0;
int nSection = 0;
// If we have a current skill being cast, use it for the attack effect
if (m_pCurSkill != null)
{
idSkill = m_pCurSkill.GetSkillID();
skillLevel = m_pCurSkill.GetSkillLevel();
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerAtkResult: Skill attack detected, skillID={idSkill}, skillLevel={skillLevel}");
}
else
{
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerAtkResult: Melee attack (m_pCurSkill is null)");
}
// TO DO: fix later
int attackTime = int.MinValue;
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] Calling PlayAttackEffect: target={pCmd.target_id}, skillID={idSkill}, skillLevel={skillLevel}, " +
$"damage={pCmd.damage}, attack_flag={pCmd.attack_flag}, speed={pCmd.speed * 50}");
PlayAttackEffect(pCmd.target_id, idSkill, skillLevel, -1, (uint)pCmd.attack_flag, pCmd.speed * 50, ref attackTime);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] PlayAttackEffect: Complete, attackTime={attackTime}");
// Only start melee work if this is a melee attack (idSkill == 0)
if (idSkill == 0)
{
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerAtkResult: Starting melee work for target={pCmd.target_id}");
if (!m_pEPWorkMan.FindWork(CECEPWorkMan.Work_type.WT_NORMAL, CECEPWork.EP_work_ID.WORK_HACKOBJECT))
{
m_pEPWorkMan.StartNormalWork(new CECEPWorkMelee(m_pEPWorkMan, pCmd.target_id));
}
}
else
{
// For skill attacks, the attack effect will use m_pCurSkill to get skill info
// The GFX system (CECAttacksMan) will handle spawning effects at hook positions
// We keep m_pCurSkill until the next cast or interruption to allow
// multiple attack results for the same skill cast (multi-hit skills)
// The skill will be cleared when:
// 1. A new skill is cast (replaced in OnMsgPlayerCastSkill)
// 2. Skill is interrupted (SKILL_INTERRUPTED message)
// 3. Player stops casting (handled by server messages)
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerAtkResult: Skill attack - GFX should be triggered via CECAttacksMan, " +
$"keeping m_pCurSkill={(m_pCurSkill != null ? m_pCurSkill.GetSkillID().ToString() : "null")}");
}
// Enter fight state
EnterFightState();
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerAtkResult: Complete");
}
/// <summary>
/// Handles skill casting messages from server for else players.
///
/// Flow:
/// 1. Server sends OBJECT_CAST_SKILL -> This handler plays cast animation
/// 2. Server sends SKILL_PERFORM -> Skill execution begins (for durative skills)
/// 3. Server sends attack result -> OnMsgPlayerAtkResult triggers PlayAttackEffect
/// 4. PlayAttackEffect -> CECAttacksMan.AddSkillAttack -> GFX system spawns effects
/// 5. Server sends SKILL_INTERRUPTED -> Clears casting state (if interrupted)
///
/// Note: Else players don't maintain skill lists, so we create temporary CECSkill objects
/// for tracking purposes only. The actual skill data comes from ElementSkill static methods.
/// </summary>
private void OnMsgPlayerCastSkill(ECMSG Msg)
{
int commandID = Convert.ToInt32(Msg.dwParam2);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerCastSkill: Entry, playerID={m_PlayerInfo.cid}, commandID={commandID}");
switch (commandID)
{
case CommandID.OBJECT_CAST_SKILL:
{
cmd_object_cast_skill pCmd =
GPDataTypeHelper.FromBytes<cmd_object_cast_skill>((byte[])Msg.dwParam1);
// Get skill object (else players don't have skill lists, so we create a temporary skill reference)
// For else players, we mainly need the skill ID for animation purposes
int skillID = pCmd.skill;
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OBJECT_CAST_SKILL: playerID={m_PlayerInfo.cid}, skillID={skillID}, target={pCmd.target}, time={pCmd.time}");
// Store current skill target
m_idCurSkillTarget = pCmd.target;
// Face the target
TurnFaceTo(pCmd.target);
// Play skill cast animation
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] Calling PlaySkillCastAction: skillID={skillID}");
bool castActionResult = PlaySkillCastAction(skillID);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] PlaySkillCastAction result: {castActionResult}");
// 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);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] Created new CECSkill: skillID={skillID}, level=1");
}
else
{
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] Reusing existing m_pCurSkill: skillID={m_pCurSkill.GetSkillID()}");
}
// Enter fight state
EnterFightState();
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OBJECT_CAST_SKILL: Complete, m_pCurSkill={(m_pCurSkill != null ? m_pCurSkill.GetSkillID().ToString() : "null")}, m_idCurSkillTarget={m_idCurSkillTarget}");
break;
}
case CommandID.OBJECT_CAST_INSTANT_SKILL:
{
cmd_object_cast_instant_skill pCmd =
GPDataTypeHelper.FromBytes<cmd_object_cast_instant_skill>((byte[])Msg.dwParam1);
int skillID = pCmd.skill;
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OBJECT_CAST_INSTANT_SKILL: playerID={m_PlayerInfo.cid}, skillID={skillID}, target={pCmd.target}");
m_idCurSkillTarget = pCmd.target;
TurnFaceTo(pCmd.target);
bool instantResult = PlaySkillCastAction(skillID);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] PlaySkillCastAction (instant) result: {instantResult}");
if (m_pCurSkill == null || m_pCurSkill.GetSkillID() != skillID)
{
m_pCurSkill = new CECSkill(skillID, 1);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] Created new CECSkill (instant): skillID={skillID}");
}
EnterFightState();
break;
}
case CommandID.OBJECT_CAST_POS_SKILL:
{
cmd_object_cast_pos_skill pCmd =
GPDataTypeHelper.FromBytes<cmd_object_cast_pos_skill>((byte[])Msg.dwParam1);
int skillID = pCmd.skill;
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] OBJECT_CAST_POS_SKILL: playerID={m_PlayerInfo.cid}, skillID={skillID}, pos=({pCmd.pos.x:F2}, {pCmd.pos.y:F2}, {pCmd.pos.z:F2})");
// For position-based skills, target is the position, not an object
// We still play the cast animation
bool posResult = PlaySkillCastAction(skillID);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] PlaySkillCastAction (pos) result: {posResult}");
if (m_pCurSkill == null || m_pCurSkill.GetSkillID() != skillID)
{
m_pCurSkill = new CECSkill(skillID, 1);
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] Created new CECSkill (pos): skillID={skillID}");
}
EnterFightState();
break;
}
case CommandID.SKILL_PERFORM:
{
// Skill perform - the skill has finished casting and is being executed
// For else players, we keep m_pCurSkill until attack result is received
// This allows PlayAttackEffect to use the skill information
// Durative skills (channeling) will continue until interrupted
int performSkillID = m_pCurSkill != null ? m_pCurSkill.GetSkillID() : 0;
bool isDurative = m_pCurSkill != null && m_pCurSkill.IsDurative();
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] SKILL_PERFORM: playerID={m_PlayerInfo.cid}, skillID={performSkillID}, isDurative={isDurative}, " +
$"m_idCurSkillTarget={m_idCurSkillTarget}");
if (m_pCurSkill != null && m_pCurSkill.IsDurative())
{
// For durative skills, we keep the skill active
// It will be cleared when SKILL_INTERRUPTED is received
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] SKILL_PERFORM: Durative skill, keeping m_pCurSkill active");
}
else if (m_pCurSkill != null && m_idCurSkillTarget != 0)
{
// For non-durative skills with a target, if server doesn't send attack result,
// we might need to trigger GFX directly from SKILL_PERFORM
// But normally attack result should come through OnMsgPlayerAtkResult
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] SKILL_PERFORM: Non-durative skill with target - " +
$"Waiting for attack result message. If no attack result arrives, GFX will not spawn!");
BMLogger.LogWarning($"[ELSEPLAYER_SKILL_FLOW] SKILL_PERFORM: WARNING - If you don't see OnMsgPlayerAtkResult logs after this, " +
$"the server is not sending attack results for else players' skills!");
}
break;
}
case CommandID.SKILL_INTERRUPTED:
{
// Skill was interrupted, clear current skill
cmd_skill_interrupted pCmd =
GPDataTypeHelper.FromBytes<cmd_skill_interrupted>((byte[])Msg.dwParam1);
int interruptedSkillID = m_pCurSkill != null ? m_pCurSkill.GetSkillID() : 0;
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] SKILL_INTERRUPTED: playerID={m_PlayerInfo.cid}, skillID={interruptedSkillID}, caster={pCmd.caster}");
if (m_pCurSkill != null)
{
StopSkillCastAction();
m_pCurSkill = null;
BMLogger.Log($"[ELSEPLAYER_SKILL_FLOW] SKILL_INTERRUPTED: Cleared m_pCurSkill and stopped cast action");
}
m_idCurSkillTarget = 0;
break;
}
default:
{
BMLogger.LogWarning($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerCastSkill: Unknown commandID={commandID} - " +
$"This might be OBJECT_SKILL_ATTACK_RESULT or another skill-related message we need to handle!");
BMLogger.LogWarning($"[ELSEPLAYER_SKILL_FLOW] OnMsgPlayerCastSkill: Unknown command - " +
$"If this is OBJECT_SKILL_ATTACK_RESULT, we need to add a handler for it to trigger GFX!");
break;
}
}
}
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 ?? "");
}
// 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<cmd_object_do_emote>((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<bool> 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<cmd_player_gather_start>((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<cmd_player_gather_stop>((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<cmd_mine_gathered>((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;
}
}
// 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
};
}