Merge remote-tracking branch 'origin/develop' into feature/chat

This commit is contained in:
CuongNV
2026-03-17 10:21:23 +07:00
24 changed files with 962 additions and 1083 deletions
@@ -50,7 +50,7 @@ ParticleSystem:
ringBufferMode: 0
ringBufferLoopRange: {x: 0, y: 1}
emitterVelocityMode: 1
looping: 1
looping: 0
prewarm: 0
playOnAwake: 1
useUnscaledTime: 0
@@ -4883,7 +4883,7 @@ ParticleSystem:
ringBufferMode: 0
ringBufferLoopRange: {x: 0, y: 1}
emitterVelocityMode: 1
looping: 1
looping: 0
prewarm: 0
playOnAwake: 1
useUnscaledTime: 0
@@ -9774,7 +9774,7 @@ ParticleSystem:
ringBufferMode: 0
ringBufferLoopRange: {x: 0, y: 1}
emitterVelocityMode: 1
looping: 1
looping: 0
prewarm: 0
playOnAwake: 1
useUnscaledTime: 0
File diff suppressed because it is too large Load Diff
@@ -4,6 +4,11 @@ using UnityEngine;
public static class GameConstants
{
public static int NUM_MAGICCLASS = 5;
public static int ARMOR_RUIN_SPEED = -25;
public static int WEAPON_RUIN_SPEED = -2;
public static float PLAYER_PRICE_SCALE = 1.0f;
public static int ENDURANCE_SCALE = 100;
}
public struct ROLEBASICPROP
@@ -2,10 +2,6 @@ namespace BrewMonster.Scripts
{
public class InventoryConst
{
// Equipment endurance scale
public const int ENDURANCE_SCALE = 100;
// NUM_MAGICCLASS
public const int NUM_MAGICCLASS = 5;
// Index of item in equipment inventory
public const int EQUIPIVTR_WEAPON = 0;
public const int EQUIPIVTR_HEAD = 1;
@@ -316,8 +316,8 @@ namespace BrewMonster.Network
}
public static void update_require_data(ref prerequisition require)
{
require.durability *= BrewMonster.Scripts.InventoryConst.ENDURANCE_SCALE;
require.max_durability *= BrewMonster.Scripts.InventoryConst.ENDURANCE_SCALE;
require.durability *= GameConstants.ENDURANCE_SCALE;
require.max_durability *= GameConstants.ENDURANCE_SCALE;
}
public static void set_to_classid(DATA_TYPE type, byte[] data, int major_type)
{
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -146,6 +146,7 @@ namespace BrewMonster
pEvent.SetDelay(dwDelayTime);
pEvent.SetReverse(bReverse);
pEvent.SetParam(param);
BMLogger.LogError("[HoangDev] bTraceTarget=" + bTraceTarget);
pEvent.SetTraceTarget(bTraceTarget);
pEvent.SetModifier(dwModifier);
pEvent.SetIsCluster(bCluster);
@@ -503,4 +504,4 @@ namespace BrewMonster
public bool bGfxDisableCamShake;
public bool bHostECMCreatedByGfx;
};
}
}
@@ -271,7 +271,6 @@ namespace BrewMonster
if (m_enumState == GfxSkillEventState.enumHit && m_bTraceTarget)
{
UpdateHitGfxTransform();
// Check if hit GFX has been destroyed (3 second lifetime) or target is destroyed
// 检查命中特效是否已被销毁(3秒生命周期)或目标是否已销毁
if (m_hitGfxInstance == null || (!m_bTargetExist && m_nTargetID != 0))
@@ -296,7 +295,26 @@ namespace BrewMonster
protected override void HitTarget(Vector3 vTarget)
{
base.HitTarget(vTarget);
DestroyFlyGfx();
// Only destroy fly GFX if NOT tracing target
// If tracing target, fly GFX will be cleaned up when buff expires
// 只有在不跟踪目标时才销毁飞行特效
// 如果跟踪目标,飞行特效将在buff过期时清理
if (!m_bTraceTarget)
{
DestroyFlyGfx();
}
else
{
// If fly GFX exists and m_bTraceTarget is true, add to tracking list
// 如果飞行特效存在且m_bTraceTarget为true,添加到跟踪列表
if (m_flyGfxInstance != null)
{
SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_flyGfxInstance, 0); // Skill ID not available, use 0
BMLogger.Log($"[TRACE_TARGET_GFX] HitTarget: Added fly GFX to trace target list (m_bTraceTarget=true)");
}
}
SpawnHitGfx(vTarget);
// TODO Phase 2: Special hit effects (rune, critical, nullity)
@@ -333,6 +351,13 @@ namespace BrewMonster
m_flyGfxInstance = GameObject.Instantiate(prefab, pos, prefab.transform.rotation);
// If m_bTraceTarget is true, add to tracking list when spawned
// 如果m_bTraceTarget为true,在生成时添加到跟踪列表
if (m_bTraceTarget)
{
SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_flyGfxInstance, 0); // Skill ID not available, use 0
BMLogger.Log($"[TRACE_TARGET_GFX] SpawnFlyGfx: Added fly GFX to trace target list (m_bTraceTarget=true)");
}
}
/// <summary>
@@ -401,6 +426,7 @@ namespace BrewMonster
// 这与C++逻辑匹配:当投射物击中地面(无目标)时使用m_szHitGrndGfx
bool bTargetExists = m_bTargetExist && m_nTargetID != 0;
GameObject prefab = bTargetExists ? m_pComposer.GetHitGFX() : m_pComposer.GetHitGrdGFX();
BMLogger.LogError("bTargetExists : " + bTargetExists);
BMLogger.LogError("HitGfx : " + m_pComposer.hitGfxName);
if (prefab == null)
@@ -429,9 +455,21 @@ namespace BrewMonster
m_hitGfxInstance = GameObject.Instantiate(prefab, vTarget, prefab.transform.rotation);
// Destroy hit GFX after 3 seconds (unless m_bTraceTarget is true, then it follows target until destroyed)
// 3秒后销毁命中特效(除非m_bTraceTarget为true否则它会跟随目标直到被销毁)
//GameObject.Destroy(m_hitGfxInstance, 3.0f);
// If m_bTraceTarget is true, add to tracking list (don't auto-destroy)
// 如果m_bTraceTarget为true添加到跟踪列表(不自动销毁)
if (m_bTraceTarget)
{
SkillGfxMan.InstanceSub?.AddTraceTargetGfx(m_hitGfxInstance, 0); // Skill ID not available, use 0
BMLogger.Log($"[TRACE_TARGET_GFX] SpawnHitGfx: Added hit GFX to trace target list (m_bTraceTarget=true)");
}
else
{
// Destroy hit GFX after 5 seconds (unless m_bTraceTarget is true, then it follows target until destroyed)
// 5秒后销毁命中特效(除非m_bTraceTarget为true,否则它会跟随目标直到被销毁)
//HIT_GFX_MAX_TIMESPAN 5000
BMLogger.Log($"[TRACE_TARGET_GFX] SpawnHitGfx: GameObject.Destroy(m_hitGfxInstance, 5.0f);");
GameObject.Destroy(m_hitGfxInstance, 5.0f);
}
}
/// <summary>
@@ -440,10 +478,28 @@ namespace BrewMonster
/// </summary>
public new void Resume()
{
DestroyFlyGfx();
// Hit GFX is auto-destroyed by Unity's Destroy timer, don't null it here
// 命中特效由Unity的Destroy计时器自动销毁,不在此处置null
m_hitGfxInstance = null;
// Don't destroy GFX if it's in trace target list
// It will be cleaned up when buff expires
// 如果GFX在跟踪目标列表中,不要销毁它
// 它将在buff过期时清理
if (m_flyGfxInstance != null)
{
if (SkillGfxMan.InstanceSub != null && !SkillGfxMan.InstanceSub.IsTraceTargetGfx(m_flyGfxInstance))
{
DestroyFlyGfx();
}
}
if (m_hitGfxInstance != null)
{
if (SkillGfxMan.InstanceSub != null && !SkillGfxMan.InstanceSub.IsTraceTargetGfx(m_hitGfxInstance))
{
// Hit GFX is auto-destroyed by Unity's Destroy timer, don't null it here
// 命中特效由Unity的Destroy计时器自动销毁,不在此处置null
m_hitGfxInstance = null;
}
}
base.Resume();
}
@@ -878,6 +934,13 @@ namespace BrewMonster
protected EC_ManPlayer m_pPlayerMan;
protected CECNPCMan m_pNPCMan;
// Track GFX instances that have m_bTraceTarget = true
// These are typically buff-related trail effects that persist until buff expires
// 跟踪具有m_bTraceTarget = true的GFX实例
// 这些通常是持续到buff结束的buff相关轨迹效果
private List<GameObject> m_TraceTargetGfxList = new List<GameObject>();
private Dictionary<GameObject, int> m_TraceTargetGfxSkillMap = new Dictionary<GameObject, int>();
public SkillGfxMan(CECGameRun pGameRun)
{
m_EventLst = new LinkedList<CECSkillGfxEvent>();
@@ -971,6 +1034,110 @@ namespace BrewMonster
{
m_FreeLst[i].Clear();
}
// Clean up trace target GFX
// 清理跟踪目标GFX
RemoveAllTraceTargetGfx();
}
/// <summary>
/// Add a GFX instance to trace target tracking list
/// 将GFX实例添加到跟踪目标跟踪列表
/// </summary>
public void AddTraceTargetGfx(GameObject gfxInstance, int skillId)
{
if (gfxInstance == null) return;
if (!m_TraceTargetGfxList.Contains(gfxInstance))
{
m_TraceTargetGfxList.Add(gfxInstance);
m_TraceTargetGfxSkillMap[gfxInstance] = skillId;
BMLogger.Log($"[TRACE_TARGET_GFX] Added GFX for skill {skillId}, total tracked: {m_TraceTargetGfxList.Count}");
}
}
/// <summary>
/// Remove all trace target GFX (called when buff states update)
/// 移除所有跟踪目标GFX(在buff状态更新时调用)
/// </summary>
public void RemoveAllTraceTargetGfx()
{
BMLogger.Log($"[TRACE_TARGET_GFX] Removing {m_TraceTargetGfxList.Count} trace target GFX");
foreach (GameObject gfx in m_TraceTargetGfxList)
{
if (gfx != null)
{
GameObject.Destroy(gfx);
}
}
m_TraceTargetGfxList.Clear();
m_TraceTargetGfxSkillMap.Clear();
}
/// <summary>
/// Remove trace target GFX for specific skill
/// 移除特定技能的跟踪目标GFX
/// </summary>
public void RemoveTraceTargetGfxForSkill(int skillId)
{
List<GameObject> toRemove = new List<GameObject>();
foreach (var kvp in m_TraceTargetGfxSkillMap)
{
if (kvp.Value == skillId)
{
toRemove.Add(kvp.Key);
}
}
foreach (GameObject gfx in toRemove)
{
if (gfx != null)
{
GameObject.Destroy(gfx);
}
m_TraceTargetGfxList.Remove(gfx);
m_TraceTargetGfxSkillMap.Remove(gfx);
}
if (toRemove.Count > 0)
{
BMLogger.Log($"[TRACE_TARGET_GFX] Removed {toRemove.Count} GFX for skill {skillId}");
}
}
/// <summary>
/// Check if a GFX instance is tracked as trace target GFX
/// 检查GFX实例是否被跟踪为跟踪目标GFX
/// </summary>
public bool IsTraceTargetGfx(GameObject gfx)
{
return m_TraceTargetGfxList.Contains(gfx);
}
/// <summary>
/// Clean up null references (GFX destroyed elsewhere)
/// 清理空引用(在其他地方销毁的GFX)
/// </summary>
public void CleanupTraceTargetGfx()
{
m_TraceTargetGfxList.RemoveAll(gfx => gfx == null);
List<GameObject> nullKeys = new List<GameObject>();
foreach (var kvp in m_TraceTargetGfxSkillMap)
{
if (kvp.Key == null)
{
nullKeys.Add(kvp.Key);
}
}
foreach (var key in nullKeys)
{
m_TraceTargetGfxSkillMap.Remove(key);
}
}
/// <summary>
@@ -201,8 +201,8 @@ namespace PerfectWorld.Scripts.Managers
StrengthReq = m_pDBEssence.require_strength;
AgilityReq = m_pDBEssence.require_agility;
ReputationReq = m_pDBEssence.require_reputation;
CurEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
CurEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
}
// Get item icon file name
public override string GetIconFile()
@@ -207,8 +207,8 @@ namespace PerfectWorld.Scripts.Managers
StrengthReq = m_pDBEssence.require_strength;
AgilityReq = m_pDBEssence.require_agility;
ReputationReq = m_pDBEssence.require_reputation;
CurEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
CurEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
}
// Get item icon file name
public override string GetIconFile()
@@ -174,8 +174,8 @@ namespace PerfectWorld.Scripts.Managers
StrengthReq = 0;
AgilityReq = 0;
ReputationReq = m_pDBEssence.require_reputation;
CurEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * ENDURANCE_SCALE;
CurEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
MaxEndurance = m_pDBEssence.durability_min * GameConstants.ENDURANCE_SCALE;
}
public override string GetIconFile()
{
@@ -14,7 +14,6 @@ using System.Text.RegularExpressions;
using System.Reflection;
using BrewMonster.Scripts.Managers;
using BrewMonster.Scripts;
namespace PerfectWorld.Scripts.Managers
{
/// <summary>
@@ -161,9 +160,6 @@ namespace PerfectWorld.Scripts.Managers
// Scale Types
public const int SCALE_SELL = 1;
// Endurance Scale
public const int ENDURANCE_SCALE = 100;
#endregion
#region Public Fields
@@ -925,7 +921,7 @@ namespace PerfectWorld.Scripts.Managers
/// </summary>
public static int VisualizeEndurance(int v)
{
return (v + ENDURANCE_SCALE - 1) / ENDURANCE_SCALE;
return (v + GameConstants.ENDURANCE_SCALE - 1) / GameConstants.ENDURANCE_SCALE;
}
/// <summary>
@@ -115,8 +115,8 @@ namespace BrewMonster.Scripts.Managers
magic_damage = dr.ReadInt();
defense = dr.ReadInt();
armor = dr.ReadInt();
resistance = new int[InventoryConst.NUM_MAGICCLASS];
for (int i = 0; i < InventoryConst.NUM_MAGICCLASS; i++)
resistance = new int[GameConstants.NUM_MAGICCLASS];
for (int i = 0; i < GameConstants.NUM_MAGICCLASS; i++)
{
resistance[i] = dr.ReadInt();
}
@@ -132,13 +132,13 @@ namespace BrewMonster.Scripts.Managers
public int[] resistance;
public IVTR_ESSENCE_ARMOR(byte[] data)
{
resistance = new int[InventoryConst.NUM_MAGICCLASS];
resistance = new int[GameConstants.NUM_MAGICCLASS];
CECDataReader dr = new(data, data.Length);
defense = dr.ReadInt();
armor = dr.ReadInt();
mp_enhance = dr.ReadInt();
hp_enhance = dr.ReadInt();
for (int i = 0; i < InventoryConst.NUM_MAGICCLASS; i++)
for (int i = 0; i < GameConstants.NUM_MAGICCLASS; i++)
{
resistance[i] = dr.ReadInt();
}
@@ -142,14 +142,22 @@ namespace PerfectWorld.Scripts.Managers
if (host != null && cid == host.GetCharacterID())
{
host.ProcessMessage(Msg);
// Call OnMsgPlayerExtState directly instead of ProcessMessage
// ProcessMessage doesn't handle MSG_PM_PLAYEREXTSTATE
// 直接调用OnMsgPlayerExtState而不是ProcessMessage
// ProcessMessage不处理MSG_PM_PLAYEREXTSTATE
host.OnMsgPlayerExtState(Msg);
}
else
{
EC_ElsePlayer elsePlayer = SeekOutElsePlayer(cid);
if (elsePlayer != null)
{
elsePlayer.ProcessMessage(Msg);
// Call OnMsgPlayerExtState directly instead of ProcessMessage
// ProcessMessage doesn't handle MSG_PM_PLAYEREXTSTATE
// 直接调用OnMsgPlayerExtState而不是ProcessMessage
// ProcessMessage不处理MSG_PM_PLAYEREXTSTATE
elsePlayer.OnMsgPlayerExtState(Msg);
}
if (Convert.ToInt32(Msg.dwParam2) == CommandID.ICON_STATE_NOTIFY && host != null && host.GetTeam() != null)
@@ -231,8 +231,7 @@ namespace BrewMonster.Scripts
{
// Constants
public const int EC_MAXNOPKLEVEL = 0; // The maximum no PK level
public const float EC_TABSEL_DIST = 60.0f; // Distance of TAB selection
public const int NUM_MAGICCLASS = 5;
public const float EC_TABSEL_DIST = 60.0f;
public const int NUM_ESBYTE = (int)(ExtendState.NUM_EXTSTATE + 31) >> 5;
// Helper methods for bit manipulation (equivalent to the C++ inline functions)
@@ -741,6 +741,15 @@ namespace BrewMonster
public virtual void SetNewExtendStates(int unknown, uint[] states, int count)
{
// TODO: Implement appearance or physics change
// Remove all trace target GFX when buff states change
// This ensures buff-related trail effects are cleaned up when buffs expire
// 当buff状态改变时移除所有跟踪目标GFX
// 这确保buff相关的轨迹效果在buff过期时被清理
if (SkillGfxMan.InstanceSub != null)
{
SkillGfxMan.InstanceSub.RemoveAllTraceTargetGfx();
}
}
public virtual void SetUpPlayer()
@@ -2202,7 +2211,45 @@ namespace BrewMonster
}
}
}
protected void OnMsgEnchantResult(ECMSG msg)
{
// 从消息中获取cmd_enchant_result结构 // Get cmd_enchant_result structure from message
cmd_enchant_result pCmd = GPDataTypeHelper.FromBytes<cmd_enchant_result>((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);
}
// 判断是否在飞行 / Check if flying
public bool IsFlying()
{
+1 -1
View File
@@ -470,7 +470,7 @@ public class CECNPC : CECObject
{
return m_pNPCModelPolicy.PlayAttackAction(nAttackSpeed, attackevent);
}
void NPCTurnFaceTo(int idTarget, float dwTime = 0.3f)
public void NPCTurnFaceTo(int idTarget, float dwTime = 0.3f)
{
if (IsDirFixed())
{
@@ -1539,6 +1539,18 @@ namespace CSNetwork.GPDataType
public byte section;
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct cmd_host_skill_attacked
{
public int idAttacker;
public int idSkill;
public int iDamage;
public byte cEquipment; // The equipment which is mangled, λιDzӦñɫ
public int attack_flag; //ǸùǷйŻͷŻػ
public byte speed; //ٶ speed * 50 ms
public byte section; // skill section
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct cmd_matter_info_list
{
public ushort count;
@@ -2929,5 +2941,6 @@ namespace CSNetwork.GPDataType
{
public int id;
};
}
@@ -1123,6 +1123,9 @@ namespace CSNetwork
case CommandID.HOST_SKILL_ATTACK_RESULT:
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLRESULT, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader);
break;
case CommandID.HOST_SKILL_ATTACKED:
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SKILLATTACKED, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader);
break;
case CommandID.CHANGE_FACE_START:
case CommandID.CHANGE_FACE_END:
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_CHANGEFACE, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader, dwDataSize);
@@ -447,36 +447,6 @@ namespace BrewMonster
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;
@@ -484,6 +454,7 @@ namespace BrewMonster
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;
@@ -510,30 +481,21 @@ namespace BrewMonster
{
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)
@@ -624,63 +586,21 @@ namespace BrewMonster
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);
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}");
// 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))
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>
@@ -699,7 +619,6 @@ namespace BrewMonster
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)
{
@@ -712,8 +631,6 @@ namespace BrewMonster
// 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;
@@ -721,9 +638,7 @@ namespace BrewMonster
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}");
PlaySkillCastAction(skillID);
// Create a temporary skill object for tracking (if needed)
// Note: Else players don't maintain skill lists like host player does
@@ -732,17 +647,11 @@ namespace BrewMonster
{
// 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:
@@ -751,18 +660,15 @@ namespace BrewMonster
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}");
PlaySkillCastAction(skillID);
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();
@@ -774,17 +680,14 @@ namespace BrewMonster
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}");
PlaySkillCastAction(skillID);
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();
@@ -796,26 +699,10 @@ namespace BrewMonster
// 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;
}
@@ -825,29 +712,21 @@ namespace BrewMonster
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)
@@ -206,11 +206,11 @@ RectTransform:
- {fileID: 6133989890986254344}
m_Father: {fileID: 1983722419643715407}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -545.9587, y: 54.83423}
m_SizeDelta: {x: -1091.9175, y: -653.2429}
m_Pivot: {x: 0.5, y: 0.5}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 500, y: 426.75708}
m_Pivot: {x: 0, y: 1}
--- !u!222 &6409592904291504631
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -458,11 +458,11 @@ RectTransform:
- {fileID: 4454076196230765805}
m_Father: {fileID: 6199635200021499044}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: -288.98}
m_SizeDelta: {x: 508.681, y: 53}
m_Pivot: {x: 0, y: 1}
--- !u!1 &4190358850504021446
GameObject:
m_ObjectHideFlags: 0
@@ -497,11 +497,11 @@ RectTransform:
- {fileID: 7473152984931807423}
m_Father: {fileID: 6199635200021499044}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: -783}
m_SizeDelta: {x: 508.681, y: 441.022}
m_Pivot: {x: 0, y: 0}
--- !u!222 &5213722908587404148
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -553,7 +553,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
inputField: {fileID: 2725480406471239880}
maxChatLength: 256
--- !u!1 &4425286880128952433
GameObject:
m_ObjectHideFlags: 0
@@ -1134,11 +1133,11 @@ RectTransform:
- {fileID: 7133716985767026273}
m_Father: {fileID: 4963429530816417249}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -746.7, y: 304}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 116, y: -0.000026464}
m_SizeDelta: {x: 160, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
m_Pivot: {x: 0, y: 1}
--- !u!222 &5054963699385999060
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -1427,7 +1426,7 @@ MonoBehaviour:
m_Spacing: 0
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 0
m_ChildControlWidth: 1
m_ChildControlHeight: 0
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
+48 -5
View File
@@ -8,7 +8,7 @@ using System;
using UnityEngine;
using static BrewMonster.Scripts.CECHPWork;
using static CECPlayerWrapper;
using PerfectWorld.Scripts.Managers;
namespace BrewMonster
{
public partial class CECHostPlayer
@@ -40,10 +40,10 @@ namespace BrewMonster
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);*/
char cEquip = (char)(pCmd.cEquipment & 0x7f);
EC_IvtrEquip pEquip = (EC_IvtrEquip)m_pEquipPack.GetItem(cEquip);
if (pEquip != null)
pEquip.AddCurEndurance(GameConstants.ARMOR_RUIN_SPEED);
}
// The host player is attacked, we should make an effect here
@@ -204,6 +204,49 @@ namespace BrewMonster
PlayAttackEffect(pCmd.idTarget, pCmd.idSkill, 0, pCmd.iDamage, (uint)pCmd.attack_flag,
pCmd.attack_speed * 50, ref refFake, pCmd.section);
}
void OnMsgHstSkillAttacked(ECMSG Msg)
{
cmd_host_skill_attacked pCmd = GPDataTypeHelper.FromBytes<cmd_host_skill_attacked>((byte[])Msg.dwParam1);
if (pCmd.iDamage != 0 && (pCmd.cEquipment & 0x7f) != 0x7f)
{
EC_IvtrEquip pEquip = (EC_IvtrEquip)m_pEquipPack.GetItem(pCmd.cEquipment & 0x7f);
if (pEquip != null)
pEquip.AddCurEndurance(GameConstants.ARMOR_RUIN_SPEED);
}
// The host player is attacked, we should make an effect here
if( GPDataTypeHelper.ISPLAYERID(pCmd.idAttacker) )
{
EC_ElsePlayer pAttacker = EC_ManMessageMono.Instance.EC_ManPlayer.GetElsePlayer(pCmd.idAttacker);
if (pAttacker)
{
if( !pAttacker.IsDead() )
{
// Face to target
pAttacker.TurnFaceTo(GetPlayerInfo().cid);
}
int refFake = 0;
pAttacker.PlayAttackEffect(GetCharacterID(), pCmd.idSkill, 0, pCmd.iDamage, (uint)pCmd.attack_flag, pCmd.speed * 50,ref refFake,pCmd.section);
pAttacker.EnterFightState();
}
}
else if( GPDataTypeHelper.ISNPCID(pCmd.idAttacker) )
{
CECNPC pAttacker = EC_ManMessageMono.Instance.CECNPCMan.GetNPC(pCmd.idAttacker);
if (pAttacker)
{
if( !pAttacker.IsDead())
{
// Face to target
pAttacker.NPCTurnFaceTo(GetPlayerInfo().cid);
}
pAttacker.PlayAttackEffect(GetCharacterID(), pCmd.idSkill, 0, pCmd.iDamage, (uint)pCmd.attack_flag, pCmd.speed,pCmd.section);
}
}
CECAutoPolicy.GetInstance().SendEvent_BeHurt(pCmd.idAttacker);
}
private void OnMsgPlayerCastSkill(ECMSG Msg)
{
+1 -39
View File
@@ -763,45 +763,7 @@ namespace BrewMonster
UnityGameSession.c2s_CmdGetItemInfo(Inventory_type.IVTRTYPE_PACK, pCmd.equip_idx);
}
private void OnMsgEnchantResult(ECMSG msg)
{
// 从消息中获取cmd_enchant_result结构 // Get cmd_enchant_result structure from message
cmd_enchant_result pCmd = GPDataTypeHelper.FromBytes<cmd_enchant_result>((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);
}
public void OnMsgHstClearTessera(ECMSG Msg)
{
+1
View File
@@ -555,6 +555,7 @@ namespace BrewMonster
case EC_MsgDef.MSG_HST_STARTATTACK: OnMsgHstStartAttack(Msg); break;
case EC_MsgDef.MSG_HST_STOPATTACK: OnMsgHstStopAttack(Msg); break;
case EC_MsgDef.MSG_HST_SKILLRESULT: OnMsgHstSkillResult(Msg); break;
case EC_MsgDef.MSG_HST_SKILLATTACKED: OnMsgHstSkillAttacked(Msg); break;
case EC_MsgDef.MSG_PM_CASTSKILL: OnMsgPlayerCastSkill(Msg); break;
case EC_MsgDef.MSG_HST_SETCOOLTIME: OnMsgHstSetCoolTime(Msg); break;
case EC_MsgDef.MSG_PM_ENCHANTRESULT: OnMsgEnchantResult(Msg); break;
File diff suppressed because one or more lines are too long