fix bug flash move skill
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d0362961b9d8f1c01d6aedb1be62d363d184135ac3c88879f13f26f078d18f46
|
||||
size 309803
|
||||
oid sha256:d381b291f0b51a7d9440c9129ad3176acc9c4e8a8ade93205cd8c87875c5b5db
|
||||
size 309973
|
||||
|
||||
@@ -552,24 +552,17 @@ namespace BrewMonster
|
||||
/// Load composer from file
|
||||
/// 从文件加载组合器
|
||||
/// </summary>
|
||||
#if UNITY_EDITOR
|
||||
public string hitGfxName;
|
||||
public string flyGfxName;
|
||||
public string hitGrdGfxName;
|
||||
#endif
|
||||
public async UniTask<bool> Load(SkillStub skillStub, string flyGFXPath, string hitGrdGFXPath, string hitGFXPath)
|
||||
{
|
||||
|
||||
#if UNITY_EDITOR
|
||||
flyGfxName = flyGFXPath;
|
||||
hitGfxName = hitGFXPath;
|
||||
hitGrdGfxName = hitGrdGFXPath;
|
||||
#else
|
||||
string flyGfxName = flyGFXPath;
|
||||
string hitGfxName = hitGFXPath;
|
||||
string hitGrdGfxName = hitGrdGFXPath;
|
||||
#endif
|
||||
// Load GFX prefabs / 加载GFX预制体
|
||||
|
||||
m_szFlyGfx = string.IsNullOrEmpty(flyGfxName) ? null : await AddressableManager.Instance.LoadPrefabAsync("gfx/" + flyGfxName);
|
||||
m_szHitGfx = string.IsNullOrEmpty(hitGfxName) ? null : await AddressableManager.Instance.LoadPrefabAsync("gfx/" + hitGfxName);
|
||||
m_szHitGrndGfx = string.IsNullOrEmpty(hitGrdGfxName) ? null : await AddressableManager.Instance.LoadPrefabAsync("gfx/" + hitGrdGfxName);
|
||||
@@ -1528,3 +1521,5 @@ public enum GfxSkillValType
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace BrewMonster
|
||||
m_fMoveTime = 0.0f;
|
||||
|
||||
iMoveMode |= (int)GPMoveMode.GP_MOVE_DEAD;
|
||||
|
||||
BMLogger.LogError($"HoangDev: SendMoveCmd m_wMoveStamp={m_wMoveStamp}");
|
||||
UnityGameSession.Instance.c2s_CmdPlayerMove(vCurPos, vCurPos, iTime/* MOVECMD_INTERVAL */, fSpeed, iMoveMode, m_wMoveStamp++);
|
||||
|
||||
m_vLastSevPos = EC_Utility.ToA3DVECTOR3(vCurPos);
|
||||
|
||||
@@ -875,10 +875,10 @@ namespace CSNetwork.GPDataType
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct cmd_host_use_item
|
||||
{
|
||||
public byte byPackage;
|
||||
public byte bySlot;
|
||||
public int item_id;
|
||||
public ushort use_count;
|
||||
public byte byPackage;
|
||||
public byte bySlot;
|
||||
public int item_id;
|
||||
public ushort use_count;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
@@ -893,9 +893,9 @@ namespace CSNetwork.GPDataType
|
||||
public string strMap;
|
||||
public A3DVECTOR3 vPos;
|
||||
//bool operator == (const ACString& rhsStr) const {return strMap == rhsStr;}
|
||||
public bool Equals(string rhsStr) {return strMap == rhsStr;}
|
||||
public bool Equals(string rhsStr) { return strMap == rhsStr; }
|
||||
//override == method
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct A3DVECTOR3
|
||||
@@ -1972,7 +1972,7 @@ namespace CSNetwork.GPDataType
|
||||
public struct cmd_player_chgshape
|
||||
{
|
||||
public int idPlayer;
|
||||
public byte shape;
|
||||
public byte shape;
|
||||
};
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct cmd_host_notify_root
|
||||
@@ -2604,7 +2604,11 @@ namespace CSNetwork.GPDataType
|
||||
public int mount_id;
|
||||
public ushort mount_color;
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct cmd_set_move_stamp
|
||||
{
|
||||
public ushort move_stamp;
|
||||
};
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct cmd_summon_plant_pet
|
||||
{
|
||||
@@ -2628,7 +2632,7 @@ namespace CSNetwork.GPDataType
|
||||
public ushort equip_idx;
|
||||
public uint cost;
|
||||
};
|
||||
|
||||
|
||||
// player leaves the world
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct cmd_player_leave_world
|
||||
|
||||
@@ -1227,6 +1227,10 @@ namespace CSNetwork
|
||||
case CommandID.PLAYER_CHGSHAPE:
|
||||
EC_ManMessage.PostMessage(EC_MsgDef.MSG_PM_PLAYERCHGSHAPE, MANAGER_INDEX.MAN_PLAYER, -1, pDataBuf, pCmdHeader);
|
||||
break;
|
||||
case CommandID.SET_MOVE_STAMP:
|
||||
|
||||
EC_ManMessage.PostMessage(EC_MsgDef.MSG_HST_SETMOVESTAMP, MANAGER_INDEX.MAN_PLAYER, 0, pDataBuf, pCmdHeader);
|
||||
break;
|
||||
default:
|
||||
#if UNITY_EDITOR
|
||||
if (isDebug)
|
||||
|
||||
@@ -590,7 +590,7 @@ namespace BrewMonster
|
||||
int iSCType = GPDataTypeHelper.FromBytes<int>(pDataBuf, offset);
|
||||
offset += sizeof(int);
|
||||
|
||||
BMLogger.Log("[MH] Loading shortcut slot: " + iSlot + " Type: " + iSCType);
|
||||
//BMLogger.Log("[MH] Loading shortcut slot: " + iSlot + " Type: " + iSCType);
|
||||
|
||||
switch ((CECShortcut.ShortcutType)iSCType)
|
||||
{
|
||||
|
||||
@@ -257,7 +257,7 @@ public class LitModelHolder : MonoSingleton<LitModelHolder>
|
||||
}
|
||||
if (!_candidatesForLoading.ContainsKey(_currentObjectToCheck))
|
||||
{
|
||||
BMLogger.Log($"LitModelHolder: Added object to candidates for loading: {_currentObjectToCheck.assetPath}");
|
||||
//BMLogger.Log($"LitModelHolder: Added object to candidates for loading: {_currentObjectToCheck.assetPath}");
|
||||
_candidatesForLoading[_currentObjectToCheck] = _realTimeSinceStartUp;
|
||||
}
|
||||
}
|
||||
@@ -267,7 +267,7 @@ public class LitModelHolder : MonoSingleton<LitModelHolder>
|
||||
{
|
||||
if (!_objectsToUnload.Contains(_currentObjectToCheck))
|
||||
{
|
||||
BMLogger.Log($"LitModelHolder: Added object to unload: {_currentObjectToCheck.assetPath}");
|
||||
//BMLogger.Log($"LitModelHolder: Added object to unload: {_currentObjectToCheck.assetPath}");
|
||||
_objectsToUnload.Add(_currentObjectToCheck);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +221,11 @@ namespace BrewMonster
|
||||
cmd_object_cast_skill pCmd =
|
||||
GPDataTypeHelper.FromBytes<cmd_object_cast_skill>((byte[])Msg.dwParam1);
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: Received OBJECT_CAST_SKILL, skillID={pCmd.skill}, " +
|
||||
// $"target={pCmd.target}, time={pCmd.time}, m_pPrepSkill={(m_pPrepSkill != null ? m_pPrepSkill.GetSkillID().ToString() : "null")}, " +
|
||||
// $"m_pCurSkill={(m_pCurSkill != null ? m_pCurSkill.GetSkillID().ToString() : "null")}, " +
|
||||
// $"IsSpellingMagic={IsSpellingMagic()}");
|
||||
|
||||
if (m_pCurSkill != null)
|
||||
{
|
||||
m_pCurSkill.EndCharging();
|
||||
@@ -235,6 +240,9 @@ namespace BrewMonster
|
||||
Debug.Assert(m_pCurSkill != null, "Current skill should not be null");
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: OBJECT_CAST_SKILL - Skill found, skillID={m_pCurSkill.GetSkillID()}, " +
|
||||
// $"IsChargeable={m_pCurSkill.IsChargeable()}");
|
||||
|
||||
if (m_pCurSkill.IsChargeable())
|
||||
m_pCurSkill.StartCharging(pCmd.time);
|
||||
@@ -247,6 +255,9 @@ namespace BrewMonster
|
||||
|
||||
pWork.PrepareCast(pCmd.target, m_pCurSkill, iWaitTime);
|
||||
m_pWorkMan.StartWork_p1(pWork);
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: OBJECT_CAST_SKILL - Created WORK_SPELLOBJECT, " +
|
||||
// $"skillID={m_pCurSkill.GetSkillID()}, target={pCmd.target}, waitTime={iWaitTime}, " +
|
||||
// $"m_pPrepSkill={(m_pPrepSkill != null ? m_pPrepSkill.GetSkillID().ToString() : "null")}");
|
||||
|
||||
// Start time counter for some type skill
|
||||
// 为某些类型的技能启动时间计数器
|
||||
@@ -288,7 +299,12 @@ namespace BrewMonster
|
||||
{
|
||||
// Skill perform
|
||||
// 技能执行
|
||||
int prepSkillIDBefore = m_pPrepSkill != null ? m_pPrepSkill.GetSkillID() : 0;
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: Received SKILL_PERFORM, " +
|
||||
// $"m_pCurSkill={(m_pCurSkill != null ? m_pCurSkill.GetSkillID().ToString() : "null")}, " +
|
||||
// $"m_pPrepSkill={(m_pPrepSkill != null ? m_pPrepSkill.GetSkillID().ToString() : "null")} (clearing)");
|
||||
m_pPrepSkill = null;
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: SKILL_PERFORM cleared m_pPrepSkill (was skillID={prepSkillIDBefore}), now null");
|
||||
|
||||
if (m_pCurSkill != null && m_pCurSkill.IsDurative())
|
||||
m_bSpellDSkill = true;
|
||||
@@ -418,6 +434,20 @@ namespace BrewMonster
|
||||
cmd_object_cast_pos_skill pCmd =
|
||||
GPDataTypeHelper.FromBytes<cmd_object_cast_pos_skill>((byte[])Msg.dwParam1);
|
||||
Debug.Assert(pCmd.caster == m_PlayerInfo.cid);
|
||||
|
||||
// Log position BEFORE flashmove processing
|
||||
// 记录闪移处理前的位置
|
||||
A3DVECTOR3 vHostPosBefore = EC_Utility.ToA3DVECTOR3(transform.position);
|
||||
BMLogger.LogError($"[DISTANCE_DEBUG] OBJECT_CAST_POS_SKILL: Received, skillID={pCmd.skill}, " +
|
||||
$"hostPosBefore=({vHostPosBefore.x:F2}, {vHostPosBefore.y:F2}, {vHostPosBefore.z:F2}), " +
|
||||
$"destPos=({pCmd.pos.x:F2}, {pCmd.pos.y:F2}, {pCmd.pos.z:F2}), " +
|
||||
$"target={pCmd.target}, distanceBefore={A3d_Magnitude(pCmd.pos - vHostPosBefore):F2}");
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: Received OBJECT_CAST_POS_SKILL, skillID={pCmd.skill}, " +
|
||||
// $"target={pCmd.target}, pos=({pCmd.pos.x:F2}, {pCmd.pos.y:F2}, {pCmd.pos.z:F2}), " +
|
||||
// $"m_pPrepSkill={(m_pPrepSkill != null ? m_pPrepSkill.GetSkillID().ToString() : "null")}, " +
|
||||
// $"m_pCurSkill={(m_pCurSkill != null ? m_pCurSkill.GetSkillID().ToString() : "null")}, " +
|
||||
// $"IsSpellingMagic={IsSpellingMagic()}, IsFlashMoving={IsFlashMoving()}");
|
||||
|
||||
CECSkill pSkill = GetNormalSkill(pCmd.skill);
|
||||
if (pSkill == null) pSkill = GetEquipSkillByID(pCmd.skill);
|
||||
@@ -426,6 +456,9 @@ namespace BrewMonster
|
||||
Debug.Assert(pSkill != null, "Skill should not be null");
|
||||
break;
|
||||
}
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: OBJECT_CAST_POS_SKILL - Skill found, skillID={pSkill.GetSkillID()}, " +
|
||||
// $"type={pSkill.GetType()}, rangeType={pSkill.GetRangeType()}");
|
||||
|
||||
TurnFaceTo(pCmd.target);
|
||||
|
||||
@@ -531,9 +564,31 @@ namespace BrewMonster
|
||||
|
||||
m_pWorkMan.StartWork_p2(pWork);
|
||||
iActionTime = nExecuteTime;
|
||||
|
||||
// Update position tracking immediately so distance checks use correct position
|
||||
// The work will move the player visually over time, but we need position tracking
|
||||
// updated now so normal attacks can check distance correctly
|
||||
// 立即更新位置跟踪,以便距离检查使用正确的位置
|
||||
// 工作将在时间上移动玩家,但我们需要现在更新位置跟踪,以便普通攻击可以正确检查距离
|
||||
m_MoveCtrl.SetHostLastPos(pCmd.pos);
|
||||
m_MoveCtrl.SetLastSevPos(pCmd.pos);
|
||||
|
||||
// Log position AFTER updating position tracking
|
||||
// 记录更新位置跟踪后的位置
|
||||
A3DVECTOR3 vHostPosAfter = EC_Utility.ToA3DVECTOR3(transform.position);
|
||||
BMLogger.LogError($"[DISTANCE_DEBUG] OBJECT_CAST_POS_SKILL: After position update, skillID={pSkill.GetSkillID()}, " +
|
||||
$"hostPosAfter=({vHostPosAfter.x:F2}, {vHostPosAfter.y:F2}, {vHostPosAfter.z:F2}), " +
|
||||
$"SetHostLastPos=({pCmd.pos.x:F2}, {pCmd.pos.y:F2}, {pCmd.pos.z:F2}), " +
|
||||
$"destPos=({pCmd.pos.x:F2}, {pCmd.pos.y:F2}, {pCmd.pos.z:F2}), " +
|
||||
$"executeTime={nExecuteTime}, positionChange={A3d_Magnitude(pCmd.pos - vHostPosBefore):F2}");
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: OBJECT_CAST_POS_SKILL - Created WORK_FLASHMOVE, " +
|
||||
// $"skillID={pSkill.GetSkillID()}, executeTime={nExecuteTime}, destPos=({pCmd.pos.x:F2}, {pCmd.pos.y:F2}, {pCmd.pos.z:F2}), " +
|
||||
// $"m_pPrepSkill={(m_pPrepSkill != null ? m_pPrepSkill.GetSkillID().ToString() : "null")}");
|
||||
}
|
||||
|
||||
bActionStartSkill = true;
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: OBJECT_CAST_POS_SKILL - Completed, skillID={pCmd.skill}");
|
||||
break;
|
||||
}
|
||||
case CommandID.PLAYER_CAST_RUNE_SKILL:
|
||||
@@ -628,8 +683,47 @@ namespace BrewMonster
|
||||
break;
|
||||
}
|
||||
case CommandID.ERROR_MESSAGE:
|
||||
{
|
||||
// Log error message from server for debugging distance issues
|
||||
// 记录来自服务器的错误消息,用于调试距离问题
|
||||
//cmd_error_message pCmd = GPDataTypeHelper.FromBytes<cmd_error_message>((byte[])Msg.dwParam1);
|
||||
//int errorCode = pCmd.message;
|
||||
|
||||
// Common error codes:
|
||||
// 2 = FIXMSG_NEEDMP (Need MP)
|
||||
// 20 = FIXMSG_NEEDITEM (Need item)
|
||||
// 21 = FIXMSG_TARGETISFAR (Target is too far)
|
||||
// 22 = FIXMSG_TARGETTOOCLOSE (Target too close)
|
||||
|
||||
A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(transform.position);
|
||||
int idCurrentTarget = m_idSelTarget;
|
||||
CECObject pTarget = idCurrentTarget > 0 ? EC_ManMessageMono.Instance.GetObject(idCurrentTarget, 1) : null;
|
||||
|
||||
if (pTarget != null)
|
||||
{
|
||||
A3DVECTOR3 vTargetPos = EC_Utility.ToA3DVECTOR3(pTarget.transform.position);
|
||||
float fDistance = A3d_Magnitude(vTargetPos - vHostPos);
|
||||
|
||||
BMLogger.LogError($"[DISTANCE_DEBUG] ERROR_MESSAGE from server: errorCode=, " +
|
||||
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
|
||||
$"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " +
|
||||
$"distance={fDistance:F2}, targetID={idCurrentTarget}, " +
|
||||
$"attackRange={m_ExtProps.ak.AttackRange:F2}, " +
|
||||
$"m_pCurSkill={(m_pCurSkill != null ? m_pCurSkill.GetSkillID().ToString() : "null")}, " +
|
||||
$"m_pPrepSkill={(m_pPrepSkill != null ? m_pPrepSkill.GetSkillID().ToString() : "null")}");
|
||||
}
|
||||
else
|
||||
{
|
||||
BMLogger.LogError($"[DISTANCE_DEBUG] ERROR_MESSAGE from server: errorCode=, " +
|
||||
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
|
||||
$"targetID={idCurrentTarget} (target object is null), " +
|
||||
$"m_pCurSkill={(m_pCurSkill != null ? m_pCurSkill.GetSkillID().ToString() : "null")}, " +
|
||||
$"m_pPrepSkill={(m_pPrepSkill != null ? m_pPrepSkill.GetSkillID().ToString() : "null")}");
|
||||
}
|
||||
|
||||
bDoOtherThing = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
Debug.Assert(false, "Unknown message type in OnMsgPlayerCastSkill");
|
||||
@@ -692,6 +786,34 @@ namespace BrewMonster
|
||||
return false;
|
||||
}
|
||||
|
||||
// Log position and distance information for debugging
|
||||
// 记录位置和距离信息用于调试
|
||||
// Use server-tracked position instead of visual position for distance checks
|
||||
// This ensures distance checks use the correct position immediately after flashmove
|
||||
// 使用服务器跟踪的位置而不是视觉位置进行距离检查
|
||||
// 这确保在闪移后立即使用正确的位置进行距离检查
|
||||
A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(m_MoveCtrl.GetLastSevPos());
|
||||
CECObject pTarget = EC_ManMessageMono.Instance.GetObject(idTarget, 1);
|
||||
|
||||
if (pTarget != null && pTarget is CECNPC cECNPC)
|
||||
{
|
||||
A3DVECTOR3 vTargetPos = EC_Utility.ToA3DVECTOR3(pTarget.transform.position);
|
||||
float fDistance = A3d_Magnitude(vTargetPos - vHostPos);
|
||||
float fAttackRange = m_ExtProps.ak.AttackRange;
|
||||
bool bCanTouch = CanTouchTarget(vHostPos,vTargetPos, cECNPC.GetTouchRadius(), 1); // 1 = melee
|
||||
|
||||
BMLogger.Log($"[DISTANCE_DEBUG] NormalAttackObject: Entry, idTarget={idTarget}, " +
|
||||
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
|
||||
$"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " +
|
||||
$"distance={fDistance:F2}, attackRange={fAttackRange:F2}, " +
|
||||
$"targetRadius={cECNPC.GetTouchRadius():F2}, CanTouch={bCanTouch}, " +
|
||||
$"bForceAttack={bForceAttack}, bMoreClose={bMoreClose}");
|
||||
}
|
||||
else
|
||||
{
|
||||
BMLogger.Log($"[DISTANCE_DEBUG] NormalAttackObject: Entry, idTarget={idTarget}, target object is null");
|
||||
}
|
||||
|
||||
//if (!EC_Game.GetGameRun().GetWorld().GetObject(idTarget, 1))
|
||||
// return false;
|
||||
bool bStartNewWork = false;
|
||||
|
||||
@@ -446,6 +446,9 @@ namespace BrewMonster
|
||||
int idSelTarget = 0 /* 0 */, int iForceAtk = -1 /* -1 */)
|
||||
{
|
||||
//StackChecker::ACTrace(4);
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: Entry, skillID={idSkill}, bCombo={bCombo}, idSelTarget={idSelTarget}, iForceAtk={iForceAtk}, " +
|
||||
// $"IsSpellingMagic={IsSpellingMagic()}, m_pPrepSkill={(m_pPrepSkill != null ? m_pPrepSkill.GetSkillID().ToString() : "null")}, " +
|
||||
// $"m_pCurSkill={(m_pCurSkill != null ? m_pCurSkill.GetSkillID().ToString() : "null")}, IsFlashMoving={IsFlashMoving()}");
|
||||
|
||||
if (m_pActionSwitcher != null)
|
||||
m_pActionSwitcher.PostMessge((int)EMsgActionSwitcher.MSG_CASTSKILL);
|
||||
@@ -462,10 +465,16 @@ namespace BrewMonster
|
||||
// return SummonPlayer(idSelTarget, bCombo);
|
||||
|
||||
if (!CanDo(ActionCanDo.CANDO_SPELLMAGIC))
|
||||
{
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: BLOCKED - CanDo(CANDO_SPELLMAGIC) returned false, skillID={idSkill}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (InSlidingState())
|
||||
{
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: BLOCKED - InSlidingState() returned true, skillID={idSkill}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bCombo)
|
||||
ClearComboSkill();
|
||||
@@ -478,8 +487,12 @@ namespace BrewMonster
|
||||
if (pSkill == null) pSkill = CECComboSkillState.Instance.GetInherentSkillByID((uint)idSkill);
|
||||
if (pSkill == null)
|
||||
{
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: BLOCKED - Skill {idSkill} not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: Skill found, skillID={pSkill.GetSkillID()}, type={pSkill.GetType()}, " +
|
||||
// $"ReadyToCast={pSkill.ReadyToCast()}, IsInstant={pSkill.IsInstant()}, IsFlashMove={pSkill.GetType() == (int)Skilltype.TYPE_FLASHMOVE}");
|
||||
|
||||
//// If we press a chargeable skill again when it's being charged,
|
||||
//// we cast it out at once
|
||||
@@ -492,8 +505,10 @@ namespace BrewMonster
|
||||
}
|
||||
|
||||
int iCon = CheckSkillCastCondition(pSkill);
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: CheckSkillCastCondition returned {iCon} for skillID={idSkill}");
|
||||
if (iCon != 0)
|
||||
{
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: BLOCKED - CheckSkillCastCondition returned error {iCon}, skillID={idSkill}");
|
||||
ProcessSkillCondition(iCon);
|
||||
return false;
|
||||
}
|
||||
@@ -657,22 +672,49 @@ namespace BrewMonster
|
||||
if (!IsMeleeing() && !IsSpellingMagic() &&
|
||||
(iTargetType == 0 || idCastTarget == m_PlayerInfo.cid))
|
||||
{
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: Entering main casting path, skillID={idSkill}, IsMeleeing={IsMeleeing()}, " +
|
||||
// $"IsSpellingMagic={IsSpellingMagic()}, iTargetType={iTargetType}, idCastTarget={idCastTarget}");
|
||||
// Cast this skill need't checking cast distance
|
||||
if (!pSkill.ReadyToCast())
|
||||
{
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: BLOCKED - ReadyToCast() returned false, skillID={idSkill}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if skill can be cast immediately (blocks casting during flash move at PRIORITY_2)
|
||||
// 检查技能是否可以立即施放(阻止在PRIORITY_2的闪移工作期间施放)
|
||||
// Also check IsFlashMoving() as an additional safeguard since client-side work might finish
|
||||
// before server processes the flashmove
|
||||
// 同时检查 IsFlashMoving() 作为额外的保护,因为客户端工作可能在服务器处理闪移之前完成
|
||||
if (IsFlashMoving() || !m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID()))
|
||||
{
|
||||
bool hasWorkOnPriority2 = m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan.Work_priority.PRIORITY_2);
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: BLOCKED - IsFlashMoving={IsFlashMoving()}, CanCastSkillImmediately={m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID())}, skillID={idSkill}, " +
|
||||
// $"IsSpellingMagic={IsSpellingMagic()}, HasWorkOnPriority2={hasWorkOnPriority2}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pSkill.IsInstant() && pSkill.GetType() != (int)Skilltype.TYPE_FLASHMOVE)
|
||||
{
|
||||
if (!NaturallyStopMoving())
|
||||
{
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: BLOCKED - NaturallyStopMoving() returned false, skillID={idSkill}");
|
||||
return false; // Couldn't stop naturally, so cancel casting skill
|
||||
}
|
||||
}
|
||||
else if (pSkill.GetType() == (int)Skilltype.TYPE_FLASHMOVE)
|
||||
{
|
||||
if (!CanDo(ActionCanDo.CANDO_FLASHMOVE))
|
||||
{
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: BLOCKED - CanDo(CANDO_FLASHMOVE) returned false, skillID={idSkill}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: Setting prep skill and calling CastSkill, skillID={idSkill}, " +
|
||||
// $"m_pPrepSkill before={(m_pPrepSkill != null ? m_pPrepSkill.GetSkillID().ToString() : "null")}");
|
||||
m_pPrepSkill = pSkill;
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: m_pPrepSkill set to skillID={idSkill}, calling CastSkill");
|
||||
CastSkill(m_PlayerInfo.cid, bForceAttack);
|
||||
}
|
||||
else if (IsSpellingMagic() && m_pCurSkill == pSkill)
|
||||
@@ -750,9 +792,17 @@ namespace BrewMonster
|
||||
|
||||
public bool CastSkill(int idTarget, bool bForceAttack, CECObject pTarget = null)
|
||||
{
|
||||
int prepSkillID = m_pPrepSkill != null ? m_pPrepSkill.GetSkillID() : 0;
|
||||
bool readyToCast = m_pPrepSkill != null ? m_pPrepSkill.ReadyToCast() : false;
|
||||
bool isSpelling = IsSpellingMagic();
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Entry, prepSkillID={prepSkillID}, idTarget={idTarget}, bForceAttack={bForceAttack}, " +
|
||||
// $"ReadyToCast={readyToCast}, IsSpellingMagic={isSpelling}, IsFlashMoving={IsFlashMoving()}");
|
||||
|
||||
// Check if prep skill is valid, ready to cast, and not currently spelling magic
|
||||
if (m_pPrepSkill == null || !m_pPrepSkill.ReadyToCast() || IsSpellingMagic())
|
||||
{
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: BLOCKED - Prep skill invalid or not ready, prepSkillID={prepSkillID}, " +
|
||||
// $"ReadyToCast={readyToCast}, IsSpellingMagic={isSpelling}");
|
||||
// Check if skill can change to melee attack
|
||||
if (m_pPrepSkill != null && m_pPrepSkill.ChangeToMelee())
|
||||
{
|
||||
@@ -787,9 +837,14 @@ namespace BrewMonster
|
||||
}
|
||||
|
||||
//TODO: Check cast condition - method not yet implemented
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: About to check conditions, m_pPrepSkill skillID={prepSkillID}, " +
|
||||
// $"m_pPrepSkill={(m_pPrepSkill != null ? m_pPrepSkill.GetSkillID().ToString() : "null")}");
|
||||
int iRet = CheckSkillCastCondition(m_pPrepSkill);
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: CheckSkillCastCondition returned {iRet} for skillID={prepSkillID}, " +
|
||||
// $"checked skillID={(m_pPrepSkill != null ? m_pPrepSkill.GetSkillID().ToString() : "null")}");
|
||||
if (iRet != 0)
|
||||
{
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: BLOCKED - CheckSkillCastCondition returned error {iRet} (2=NeedMP, 8=NeedAP, 10=PackFull, 20=NeedItem, 12=HPUnsatisfied), skillID={prepSkillID}");
|
||||
switch (iRet)
|
||||
{
|
||||
case 2: // Need MP
|
||||
@@ -802,6 +857,9 @@ namespace BrewMonster
|
||||
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_PACKFULL1);
|
||||
break;
|
||||
case 20: // Need item
|
||||
// Debug.LogError($"[SKILL_CAST_DEBUG] CastSkill: ERROR 20 - Need item for skillID={prepSkillID}, " +
|
||||
// $"ItemCost={m_pPrepSkill.SkillCore?.GetItemCost() ?? 0}, " +
|
||||
// $"ItemTotalNum={(m_pPrepSkill.SkillCore != null ? GetPack().GetItemTotalNum(m_pPrepSkill.SkillCore.GetItemCost()) : 0)}");
|
||||
// g_pGame.GetGameRun().AddFixedMessage(FIXMSG_NEEDITEM);
|
||||
break;
|
||||
case 12: // HP unsatisfied
|
||||
@@ -815,7 +873,8 @@ namespace BrewMonster
|
||||
|
||||
byte byPVPMask = glb_BuildPVPMask(bForceAttack);
|
||||
|
||||
Debug.LogError($"HoangDev: Cast Skill ID={m_pPrepSkill.GetSkillID()}");
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Sending skill to server, skillID={prepSkillID}, idTarget={idTarget}, " +
|
||||
// $"byPVPMask={byPVPMask}, IsInstant={m_pPrepSkill.IsInstant()}, IsFlashMove={m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_FLASHMOVE}");
|
||||
|
||||
// Handle instant skills
|
||||
if (m_pPrepSkill.IsInstant())
|
||||
@@ -823,9 +882,11 @@ namespace BrewMonster
|
||||
int countTarget = 1;
|
||||
targetsCastSkill = new int[countTarget];
|
||||
targetsCastSkill[0] = idTarget;
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Sending c2s_CmdCastInstantSkill, skillID={prepSkillID}, target={idTarget}, count={countTarget}");
|
||||
UnityGameSession.c2s_CmdCastInstantSkill(m_pPrepSkill.GetSkillID(), byPVPMask, countTarget,
|
||||
targetsCastSkill);
|
||||
m_pPrepSkill = null;
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Instant skill sent, m_pPrepSkill cleared");
|
||||
}
|
||||
// Handle flash move skills (瞬移技能)
|
||||
else if (m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_FLASHMOVE)
|
||||
@@ -855,9 +916,22 @@ namespace BrewMonster
|
||||
|
||||
fDist = Mathf.Abs(fDist);
|
||||
A3DVECTOR3 vDest = m_MoveCtrl.FlashMove(vDir, 100.0f, fDist);
|
||||
UnityGameSession.c2s_CmdCastPosSkill(m_pPrepSkill.GetSkillID(), EC_Utility.ToVector3(vDest),
|
||||
byPVPMask, 0, 0);
|
||||
UnityEngine.Vector3 vDestVec3 = EC_Utility.ToVector3(vDest);
|
||||
|
||||
// Log position information before sending flashmove
|
||||
// 在发送闪移前记录位置信息
|
||||
A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(transform.position);
|
||||
BMLogger.Log($"[DISTANCE_DEBUG] CastSkill: Before sending c2s_CmdCastPosSkill (flashmove self), skillID={prepSkillID}, " +
|
||||
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
|
||||
$"destPos=({vDestVec3.x:F2}, {vDestVec3.y:F2}, {vDestVec3.z:F2}), " +
|
||||
$"flashDistance={fDist:F2}, byPVPMask={byPVPMask}");
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Sending c2s_CmdCastPosSkill (flashmove self), skillID={prepSkillID}, " +
|
||||
// $"pos=({vDestVec3.x:F2}, {vDestVec3.y:F2}, {vDestVec3.z:F2}), byPVPMask={byPVPMask}");
|
||||
UnityGameSession.c2s_CmdCastPosSkill(m_pPrepSkill.GetSkillID(), vDestVec3, byPVPMask, 0, 0);
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Flashmove (self) sent, clearing m_pPrepSkill (was skillID={prepSkillID})");
|
||||
m_pPrepSkill = null;
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Flashmove (self) complete, m_pPrepSkill is now null");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -979,12 +1053,39 @@ namespace BrewMonster
|
||||
}
|
||||
|
||||
// 发送协议 (Send protocol)
|
||||
UnityGameSession.c2s_CmdCastPosSkill(m_pPrepSkill.GetSkillID(), EC_Utility.ToVector3(vMovePos),
|
||||
byPVPMask, 1, idTarget);
|
||||
UnityEngine.Vector3 vMovePosVec3 = EC_Utility.ToVector3(vMovePos);
|
||||
|
||||
// Log position information before sending flashmove
|
||||
// 在发送闪移前记录位置信息
|
||||
A3DVECTOR3 vHostPos2 = EC_Utility.ToA3DVECTOR3(transform.position);
|
||||
CECObject pTarget2 = idTarget > 0 ? EC_ManMessageMono.Instance.GetObject(idTarget, 1) : null;
|
||||
if (pTarget2 != null)
|
||||
{
|
||||
A3DVECTOR3 vTargetPos2 = EC_Utility.ToA3DVECTOR3(pTarget2.transform.position);
|
||||
float fDistance2 = A3d_Magnitude(vTargetPos2 - vHostPos2);
|
||||
BMLogger.Log($"[DISTANCE_DEBUG] CastSkill: Before sending c2s_CmdCastPosSkill (flashmove target), skillID={prepSkillID}, " +
|
||||
$"hostPos=({vHostPos2.x:F2}, {vHostPos2.y:F2}, {vHostPos2.z:F2}), " +
|
||||
$"targetPos=({vTargetPos2.x:F2}, {vTargetPos2.y:F2}, {vTargetPos2.z:F2}), " +
|
||||
$"destPos=({vMovePosVec3.x:F2}, {vMovePosVec3.y:F2}, {vMovePosVec3.z:F2}), " +
|
||||
$"distance={fDistance2:F2}, target={idTarget}, byPVPMask={byPVPMask}");
|
||||
}
|
||||
else
|
||||
{
|
||||
BMLogger.Log($"[DISTANCE_DEBUG] CastSkill: Before sending c2s_CmdCastPosSkill (flashmove target), skillID={prepSkillID}, " +
|
||||
$"hostPos=({vHostPos2.x:F2}, {vHostPos2.y:F2}, {vHostPos2.z:F2}), " +
|
||||
$"destPos=({vMovePosVec3.x:F2}, {vMovePosVec3.y:F2}, {vMovePosVec3.z:F2}), " +
|
||||
$"target={idTarget} (target object is null), byPVPMask={byPVPMask}");
|
||||
}
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Sending c2s_CmdCastPosSkill (flashmove target), skillID={prepSkillID}, " +
|
||||
// $"pos=({vMovePosVec3.x:F2}, {vMovePosVec3.y:F2}, {vMovePosVec3.z:F2}), target={idTarget}, byPVPMask={byPVPMask}");
|
||||
UnityGameSession.c2s_CmdCastPosSkill(m_pPrepSkill.GetSkillID(), vMovePosVec3, byPVPMask, 1, idTarget);
|
||||
bSuccess = true;
|
||||
}
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Flashmove (target) sent, clearing m_pPrepSkill (was skillID={prepSkillID}), bSuccess={bSuccess}");
|
||||
m_pPrepSkill = null;
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Flashmove (target) complete, m_pPrepSkill is now null");
|
||||
return bSuccess;
|
||||
}
|
||||
}
|
||||
@@ -995,7 +1096,39 @@ namespace BrewMonster
|
||||
int targets = 1;
|
||||
targetsCastSkill = new int[targets];
|
||||
targetsCastSkill[0] = idTarget;
|
||||
|
||||
// Log position and distance information before sending skill cast
|
||||
// Use server-tracked position instead of visual position for accurate distance checks
|
||||
// 在发送技能施放前记录位置和距离信息
|
||||
// 使用服务器跟踪的位置而不是视觉位置进行准确的距离检查
|
||||
A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(m_MoveCtrl.GetLastSevPos());
|
||||
CECObject pTarget1 = idTarget > 0 ? EC_ManMessageMono.Instance.GetObject(idTarget, 1) : null;
|
||||
|
||||
if (pTarget != null && pTarget1 is CECNPC cECNPC)
|
||||
{
|
||||
A3DVECTOR3 vTargetPos = EC_Utility.ToA3DVECTOR3(pTarget.transform.position);
|
||||
float fDistance = A3d_Magnitude(vTargetPos - vHostPos);
|
||||
float fSkillRange = m_pPrepSkill.GetCastRange(m_ExtProps.ak.AttackRange, GetPrayDistancePlus());
|
||||
bool bCanTouch = CanTouchTarget(vTargetPos, cECNPC.GetTouchRadius(), 2); // 2 = skill
|
||||
|
||||
BMLogger.Log($"[DISTANCE_DEBUG] CastSkill: Before sending c2s_CmdCastSkill (regular), skillID={prepSkillID}, " +
|
||||
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
|
||||
$"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " +
|
||||
$"distance={fDistance:F2}, skillRange={fSkillRange:F2}, " +
|
||||
$"targetRadius={cECNPC.GetTouchRadius():F2}, CanTouch={bCanTouch}, " +
|
||||
$"target={idTarget}, byPVPMask={byPVPMask2}");
|
||||
}
|
||||
else
|
||||
{
|
||||
BMLogger.Log($"[DISTANCE_DEBUG] CastSkill: Before sending c2s_CmdCastSkill (regular), skillID={prepSkillID}, " +
|
||||
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
|
||||
$"target={idTarget} (target object is null), byPVPMask={byPVPMask2}");
|
||||
}
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Sending c2s_CmdCastSkill (regular), skillID={prepSkillID}, " +
|
||||
// $"target={idTarget}, count={targets}, byPVPMask={byPVPMask2}");
|
||||
UnityGameSession.c2s_CmdCastSkill(m_pPrepSkill.GetSkillID(), byPVPMask2, targets, targetsCastSkill);
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CastSkill: Regular skill sent, m_pPrepSkill still set (will be cleared on server response)");
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1155,12 +1288,18 @@ namespace BrewMonster
|
||||
// 13=combo skill not active, 20=need item
|
||||
public int CheckSkillCastCondition(CECSkill pSkill)
|
||||
{
|
||||
int skillID = pSkill != null ? pSkill.GetSkillID() : 0;
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CheckSkillCastCondition: Entry, skillID={skillID}, SkillCore={(pSkill.SkillCore != null ? "not null" : "null")}");
|
||||
|
||||
// Check if skill requires an item
|
||||
if (pSkill.SkillCore != null)
|
||||
{
|
||||
int idItem = pSkill.SkillCore.GetItemCost();
|
||||
if (idItem > 0 && GetPack().GetItemTotalNum(idItem) <= 0)
|
||||
int itemTotalNum = GetPack().GetItemTotalNum(idItem);
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CheckSkillCastCondition: Item check, skillID={skillID}, idItem={idItem}, itemTotalNum={itemTotalNum}");
|
||||
if (idItem > 0 && itemTotalNum <= 0)
|
||||
{
|
||||
// Debug.LogError($"[SKILL_CAST_DEBUG] CheckSkillCastCondition: ERROR 20 - Need item, skillID={skillID}, requiredItem={idItem}, have={itemTotalNum}");
|
||||
return 20; // Need item
|
||||
}
|
||||
}
|
||||
@@ -1215,9 +1354,14 @@ namespace BrewMonster
|
||||
|
||||
if (pSkill.SkillCore != null)
|
||||
{
|
||||
return pSkill.SkillCore.Condition((uint)pSkill.GetSkillID(), Info, pSkill.GetSkillLevel());
|
||||
int conditionResult = pSkill.SkillCore.Condition((uint)pSkill.GetSkillID(), Info, pSkill.GetSkillLevel());
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CheckSkillCastCondition: SkillCore.Condition returned {conditionResult} for skillID={skillID}, " +
|
||||
// $"MP={Info.mp}, AP={Info.ap}, HP={Info.hp}/{Info.max_hp}, weapon={Info.weapon}, arrow={Info.arrow}, " +
|
||||
// $"is_combat={Info.is_combat}, move_env={Info.move_env}");
|
||||
return conditionResult;
|
||||
}
|
||||
|
||||
// Debug.Log($"[SKILL_CAST_DEBUG] CheckSkillCastCondition: Success (no SkillCore), skillID={skillID}");
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,12 @@ namespace BrewMonster
|
||||
|
||||
m_MoveCtrl.SetMoveStamp(pCmd.stamp);
|
||||
}
|
||||
|
||||
public void OnMsgHstSetMoveStamp(ECMSG Msg)
|
||||
{
|
||||
cmd_set_move_stamp pCmd = GPDataTypeHelper.FromBytes<cmd_set_move_stamp>((byte[])Msg.dwParam1);
|
||||
BMLogger.LogError($"OnMsgHstSetMoveStamp: Received move stamp {pCmd.move_stamp}");
|
||||
m_MoveCtrl.SetMoveStamp(pCmd.move_stamp);
|
||||
}
|
||||
public void OnMsgHstGoto(in ECMSG Msg)
|
||||
{
|
||||
PopupManager.Instance.OnPlayerRevived();
|
||||
|
||||
@@ -611,6 +611,8 @@ namespace BrewMonster
|
||||
case EC_MsgDef.MSG_PM_DUELOPT: OnMsgHstDuelOpt(Msg); break;
|
||||
case EC_MsgDef.MSG_PM_PLAYERCHGSHAPE :
|
||||
OnMsgPlayerChgShape(Msg); break;
|
||||
case EC_MsgDef.MSG_HST_SETMOVESTAMP: OnMsgHstSetMoveStamp(Msg); break;
|
||||
|
||||
default:
|
||||
// Uncomment to debug unhandled messages
|
||||
// Debug.LogWarning($"[CECHostPlayer] ProcessMessage: Unhandled message {msg}");
|
||||
@@ -1411,11 +1413,14 @@ namespace BrewMonster
|
||||
float fMaxCut = 1.0f)
|
||||
{
|
||||
float fDist = A3d_Magnitude(vTargetPos - vHostPos);
|
||||
bool bResult = false;
|
||||
float fRange = 0.0f;
|
||||
string reasonStr = iReason == 1 ? "melee" : (iReason == 2 ? "cast magic" : (iReason == 3 ? "talk" : $"unknown({iReason})"));
|
||||
|
||||
switch (iReason)
|
||||
{
|
||||
case 1: // melee
|
||||
{
|
||||
float fRange;
|
||||
if (fMaxCut >= 0.0f)
|
||||
{
|
||||
float fCutDist = m_ExtProps.ak.AttackRange * 0.3f;
|
||||
@@ -1430,7 +1435,16 @@ namespace BrewMonster
|
||||
}
|
||||
|
||||
if (fDist - fTargetRad <= fRange)
|
||||
{
|
||||
bResult = true;
|
||||
BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " +
|
||||
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
|
||||
$"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " +
|
||||
$"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " +
|
||||
$"requiredRange={fRange:F2}, effectiveDistance={fDist - fTargetRad:F2}, " +
|
||||
$"result={bResult}, attackRange={m_ExtProps.ak.AttackRange:F2}");
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -1440,35 +1454,69 @@ namespace BrewMonster
|
||||
{
|
||||
|
||||
//TODO : Check this function GetCastRange
|
||||
float fRange = m_pPrepSkill.GetCastRange(m_ExtProps.ak.AttackRange, GetPrayDistancePlus());
|
||||
fRange = m_pPrepSkill.GetCastRange(m_ExtProps.ak.AttackRange, GetPrayDistancePlus());
|
||||
|
||||
if (fRange > 0.0f)
|
||||
{
|
||||
if (fDist - fTargetRad <= fRange)
|
||||
{
|
||||
bResult = true;
|
||||
BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " +
|
||||
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
|
||||
$"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " +
|
||||
$"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " +
|
||||
$"requiredRange={fRange:F2}, effectiveDistance={fDist - fTargetRad:F2}, " +
|
||||
$"result={bResult}, skillID={m_pPrepSkill.GetSkillID()}, attackRange={m_ExtProps.ak.AttackRange:F2}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bResult = true;
|
||||
BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " +
|
||||
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
|
||||
$"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " +
|
||||
$"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " +
|
||||
$"requiredRange=unlimited, result={bResult}, skillID={m_pPrepSkill.GetSkillID()}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // talk
|
||||
{
|
||||
if (fDist - fTargetRad <= 5.0f)
|
||||
fRange = 5.0f;
|
||||
if (fDist - fTargetRad <= fRange)
|
||||
{
|
||||
bResult = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: // no special reason
|
||||
{
|
||||
if (fDist < (fTargetRad + m_fTouchRad) * 3.0f)
|
||||
fRange = (fTargetRad + m_fTouchRad) * 3.0f;
|
||||
if (fDist < fRange)
|
||||
{
|
||||
bResult = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Log distance check result for debugging
|
||||
// 记录距离检查结果用于调试
|
||||
BMLogger.Log($"[DISTANCE_DEBUG] CanTouchTarget: reason={reasonStr}, " +
|
||||
$"hostPos=({vHostPos.x:F2}, {vHostPos.y:F2}, {vHostPos.z:F2}), " +
|
||||
$"targetPos=({vTargetPos.x:F2}, {vTargetPos.y:F2}, {vTargetPos.z:F2}), " +
|
||||
$"distance={fDist:F2}, targetRadius={fTargetRad:F2}, " +
|
||||
$"requiredRange={fRange:F2}, effectiveDistance={fDist - fTargetRad:F2}, " +
|
||||
$"result={bResult}, attackRange={m_ExtProps.ak.AttackRange:F2}");
|
||||
|
||||
return false;
|
||||
}
|
||||
public void RemoveObjectFromTabSels(CECObject pObject)
|
||||
@@ -1485,8 +1533,11 @@ namespace BrewMonster
|
||||
|
||||
public bool CanTouchTarget(A3DVECTOR3 vTargetPos, float fTargetRad, int iReason, float fMaxCut = 1.0f)
|
||||
{
|
||||
A3DVECTOR3 vector = new A3DVECTOR3(playerTransform.position.x, playerTransform.position.y,
|
||||
playerTransform.position.z);
|
||||
// Use server-tracked position instead of visual position for distance checks
|
||||
// This ensures distance checks use the correct position immediately after flashmove
|
||||
// 使用服务器跟踪的位置而不是视觉位置进行距离检查
|
||||
// 这确保在闪移后立即使用正确的位置进行距离检查
|
||||
A3DVECTOR3 vector = EC_Utility.ToA3DVECTOR3(m_MoveCtrl.GetLastSevPos());
|
||||
return CanTouchTarget(vector, vTargetPos, fTargetRad, iReason, fMaxCut);
|
||||
}
|
||||
|
||||
|
||||
+545
-9
File diff suppressed because one or more lines are too long
@@ -0,0 +1,78 @@
|
||||
# Flash Move Skill Protocol Comparison: C++ vs C#
|
||||
|
||||
## Summary
|
||||
**Both C++ and C# send the same command ID (89) for CAST_POS_SKILL**, but they use different protocol wrappers.
|
||||
|
||||
## C++ Implementation
|
||||
|
||||
### Log Evidence (EC.log)
|
||||
```
|
||||
[17:10:03.501] [FLASH_SKILL_PROTOCOL] c2s_SendCmdCastPosSkill: Sending protocol C2S::CAST_POS_SKILL (command ID=89), skillID=58, pos=(858.33, 60.93, -149.44), byPVPMask=0, targetCount=0
|
||||
[17:10:03.501] [FLASH_SKILL_PROTOCOL] c2s_SendCmdCastPosSkill: Protocol packet size=20 bytes
|
||||
[17:10:03.501] CLIENT - CAST_POS_SKILL(89)
|
||||
```
|
||||
|
||||
### Code Location
|
||||
- **File:** `EC_SendC2SCmds.cpp`
|
||||
- **Function:** `c2s_SendCmdCastPosSkill()` (line 1030-1068)
|
||||
- **Protocol:** Sends `C2S::CAST_POS_SKILL` directly as protocol command
|
||||
- **Command ID:** 89 (from enum `C2S::CAST_POS_SKILL`)
|
||||
- **Packet Structure:**
|
||||
```cpp
|
||||
[cmd_header] + [cmd_cast_pos_skill]
|
||||
- cmd_header.cmd = C2S::CAST_POS_SKILL (89)
|
||||
- cmd_cast_pos_skill contains: skill_id, pos, force_attack, target_count, targets[]
|
||||
```
|
||||
|
||||
## C# Implementation
|
||||
|
||||
### Log Evidence (SessionLog)
|
||||
```
|
||||
[16:58:15.286] [DISTANCE_DEBUG] CastSkill: Before sending c2s_CmdCastPosSkill (flashmove self), skillID=58, hostPos=(860.71, 59.59, -130.28), destPos=(860.62, 60.73, -145.39), flashDistance=16.00, byPVPMask=0
|
||||
[16:58:15.286] [GameSession] Sending protocol: gamedatasend (Type: PROTOCOL_GAMEDATASEND) + Type=34 - CMD_ID: CAST_POS_SKILL
|
||||
```
|
||||
|
||||
### Code Location
|
||||
- **File:** `C2SCommand.cs`
|
||||
- **Enum:** `CommandID.CAST_POS_SKILL = 89` (line 118)
|
||||
- **Protocol:** Wraps command in `PROTOCOL_GAMEDATASEND` (Type=34)
|
||||
- **Command ID:** 89 (stored in first 2 bytes of Data field)
|
||||
- **Packet Structure:**
|
||||
```csharp
|
||||
[PROTOCOL_GAMEDATASEND header] + [Data field]
|
||||
- Protocol Type = 34 (PROTOCOL_GAMEDATASEND)
|
||||
- Data[0-1] = CommandID.CAST_POS_SKILL (89) as ushort
|
||||
- Data[2+] = CMD_CastPosSkill structure (skillId, pos, pvpMask, targetCount, targets[])
|
||||
```
|
||||
|
||||
## Key Differences
|
||||
|
||||
| Aspect | C++ | C# |
|
||||
|--------|-----|-----|
|
||||
| **Protocol Type** | Direct command (89) | Wrapped in PROTOCOL_GAMEDATASEND (34) |
|
||||
| **Command ID** | 89 (C2S::CAST_POS_SKILL) | 89 (CommandID.CAST_POS_SKILL) |
|
||||
| **Packet Structure** | `[cmd_header][cmd_cast_pos_skill]` | `[PROTOCOL_GAMEDATASEND][Data with CMD_ID][CMD_CastPosSkill]` |
|
||||
| **Packet Size** | 20 bytes (for skillID=58, no targets) | Larger (includes PROTOCOL_GAMEDATASEND wrapper) |
|
||||
|
||||
## Conclusion
|
||||
|
||||
✅ **The command ID is the same (89)** in both implementations.
|
||||
|
||||
⚠️ **The protocol wrapper is different:**
|
||||
- **C++** sends the command directly as protocol 89
|
||||
- **C#** wraps it in `PROTOCOL_GAMEDATASEND` (34) and puts the command ID (89) in the Data field
|
||||
|
||||
This is a **protocol architecture difference**, not a bug. The C# implementation uses a unified `GameDataSend` protocol that wraps all game commands, while the C++ implementation sends commands directly. The server should handle both formats correctly if it recognizes:
|
||||
1. Direct protocol 89 (C++ style)
|
||||
2. Protocol 34 with command ID 89 in the data (C# style)
|
||||
|
||||
## Verification
|
||||
|
||||
Both logs show:
|
||||
- **Skill ID:** 58 (same flash move skill)
|
||||
- **Command ID:** 89 (CAST_POS_SKILL)
|
||||
- **Position data:** Present in both
|
||||
- **PVP Mask:** 0 in both
|
||||
- **Target Count:** 0 in both
|
||||
|
||||
The actual command data is **identical**; only the protocol wrapper differs.
|
||||
@@ -0,0 +1,262 @@
|
||||
# Flash Move Skill: Server Protocol Responses Comparison
|
||||
|
||||
## Summary
|
||||
After the client sends `CAST_POS_SKILL` (command ID 89), the server responds with a sequence of protocols. This document compares the server responses received by both C++ and C# clients.
|
||||
|
||||
## Client Request (Both Same)
|
||||
|
||||
### C++ Client
|
||||
```
|
||||
[17:10:03.501] CLIENT - CAST_POS_SKILL(89)
|
||||
```
|
||||
|
||||
### C# Client
|
||||
```
|
||||
[16:58:15.286] [GameSession] Sending protocol: gamedatasend (Type: PROTOCOL_GAMEDATASEND) + Type=34 - CMD_ID: CAST_POS_SKILL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server Response Sequence
|
||||
|
||||
### Immediate Response (Within ~64ms)
|
||||
|
||||
| Order | C++ Log | C# Log | Command ID | Command Name | Status |
|
||||
|-------|---------|--------|------------|--------------|--------|
|
||||
| 1-9 | OBJECT_MOVE(15) × 9 | CMDID 15 × 6 | 15 | OBJECT_MOVE | ✅ Same |
|
||||
| 10 | OBJECT_STOP_MOVE(35) | CMDID 35 | 35 | OBJECT_STOP_MOVE | ✅ Same |
|
||||
| 11 | SET_MOVE_STAMP(205) | CMDID 205 (Unhandled) | 205 | SET_MOVE_STAMP | ⚠️ C# Unhandled |
|
||||
| 12 | OBJECT_CAST_POS_SKILL(204) | CMDID 204 | 204 | OBJECT_CAST_POS_SKILL | ✅ Same |
|
||||
| 13 | SKILL_PERFORM(88) | CMDID 88 | 88 | SKILL_PERFORM | ✅ Same |
|
||||
| 14 | SET_COOLDOWN(198) | CMDID 198 | 198 | SET_COOLDOWN | ✅ Same |
|
||||
| 15 | COMBO_SKILL_PREPARE(394) | CMDID 394 | 394 | COMBO_SKILL_PREPARE | ✅ Same |
|
||||
| 16 | ENCHANT_RESULT(139) | CMDID 139 | 139 | ENCHANT_RESULT | ✅ Same |
|
||||
| 17 | SELF_INFO_00(38) | CMDID 38 | 38 | SELF_INFO_00 | ✅ Same |
|
||||
| 18 | UPDATE_EXT_STATE(124) | CMDID 124 (Unhandled) | 124 | UPDATE_EXT_STATE | ⚠️ C# Unhandled |
|
||||
| 19 | ICON_STATE_NOTIFY(125) | CMDID 125 (Unhandled) | 125 | ICON_STATE_NOTIFY | ⚠️ C# Unhandled |
|
||||
|
||||
### Delayed Response (~1 second later)
|
||||
|
||||
| Order | C++ Log | C# Log | Command ID | Command Name | Status |
|
||||
|-------|---------|--------|------------|--------------|--------|
|
||||
| 20 | OBJECT_MOVE(15) × 2 | CMDID 15 × 2 | 15 | OBJECT_MOVE | ✅ Same |
|
||||
| 21 | OBJECT_STOP_MOVE(35) | CMDID 35 | 35 | OBJECT_STOP_MOVE | ✅ Same |
|
||||
| 22 | OBJECT_MOVE(15) × 2 | CMDID 15 × 2 | 15 | OBJECT_MOVE | ✅ Same |
|
||||
| 23 | HOST_STOP_SKILL(123) | CMDID 123 | 123 | HOST_STOP_SKILL | ✅ Same |
|
||||
| 24 | UPDATE_EXT_STATE(124) | CMDID 124 (Unhandled) | 124 | UPDATE_EXT_STATE | ⚠️ C# Unhandled |
|
||||
| 25 | ICON_STATE_NOTIFY(125) | CMDID 125 (Unhandled) | 125 | ICON_STATE_NOTIFY | ⚠️ C# Unhandled |
|
||||
|
||||
---
|
||||
|
||||
## Detailed Protocol Analysis
|
||||
|
||||
### ✅ Fully Handled Protocols (Both Implementations)
|
||||
|
||||
#### 1. OBJECT_MOVE (15)
|
||||
- **C++:** `SERVER - OBJECT_MOVE(15), size=21`
|
||||
- **C#:** `### GameDataSend: CMDID 15`
|
||||
- **Purpose:** Updates object position during flash move
|
||||
- **Status:** ✅ Both handle correctly
|
||||
|
||||
#### 2. OBJECT_STOP_MOVE (35)
|
||||
- **C++:** `SERVER - OBJECT_STOP_MOVE(35), size=20`
|
||||
- **C#:** `### GameDataSend: CMDID 35`
|
||||
- **Purpose:** Stops object movement
|
||||
- **Status:** ✅ Both handle correctly
|
||||
|
||||
#### 3. OBJECT_CAST_POS_SKILL (204)
|
||||
- **C++:** `SERVER - OBJECT_CAST_POS_SKILL(204), size=27`
|
||||
- **C#:** `### GameDataSend: CMDID 204`
|
||||
- **Purpose:** Confirms the position skill cast
|
||||
- **Status:** ✅ Both handle correctly
|
||||
- **C++ Debug Log:**
|
||||
```
|
||||
[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: Received OBJECT_CAST_POS_SKILL,
|
||||
skillID=58, target=0, pos=(858.33, 60.93, -149.44)
|
||||
```
|
||||
- **C# Debug Log:**
|
||||
```
|
||||
[DISTANCE_DEBUG] OBJECT_CAST_POS_SKILL: Received, skillID=58,
|
||||
hostPosBefore=(860.71, 59.59, -130.28), destPos=(860.62, 60.73, -145.39)
|
||||
```
|
||||
|
||||
#### 4. SKILL_PERFORM (88)
|
||||
- **C++:** `SERVER - SKILL_PERFORM(88), size=0`
|
||||
- **C#:** `### GameDataSend: CMDID 88`
|
||||
- **Purpose:** Signals skill execution completion
|
||||
- **Status:** ✅ Both handle correctly
|
||||
- **C++ Debug Log:**
|
||||
```
|
||||
[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: Received SKILL_PERFORM,
|
||||
m_pCurSkill=0, m_pPrepSkill=0 (clearing)
|
||||
```
|
||||
|
||||
#### 5. SET_COOLDOWN (198)
|
||||
- **C++:** `SERVER - SET_COOLDOWN(198), size=8`
|
||||
- **C#:** `### GameDataSend: CMDID 198`
|
||||
- **Purpose:** Sets skill cooldown timer
|
||||
- **Status:** ✅ Both handle correctly
|
||||
|
||||
#### 6. COMBO_SKILL_PREPARE (394)
|
||||
- **C++:** `SERVER - COMBO_SKILL_PREPARE(394), size=20`
|
||||
- **C#:** `### GameDataSend: CMDID 394`
|
||||
- **Purpose:** Prepares combo skill chain
|
||||
- **Status:** ✅ Both handle correctly
|
||||
- **C++ Log:** `Receive COMBO_SKILL_PREPARE cmd from server.`
|
||||
|
||||
#### 7. ENCHANT_RESULT (139)
|
||||
- **C++:** `SERVER - ENCHANT_RESULT(139), size=19`
|
||||
- **C#:** `### GameDataSend: CMDID 139`
|
||||
- **Purpose:** Returns enchantment/effect result
|
||||
- **Status:** ✅ Both handle correctly
|
||||
|
||||
#### 8. SELF_INFO_00 (38)
|
||||
- **C++:** `SERVER - SELF_INFO_00(38), size=36`
|
||||
- **C#:** `### GameDataSend: CMDID 38`
|
||||
- **Purpose:** Updates host player basic info
|
||||
- **Status:** ✅ Both handle correctly
|
||||
|
||||
#### 9. HOST_STOP_SKILL (123)
|
||||
- **C++:** `SERVER - HOST_STOP_SKILL(123), size=0`
|
||||
- **C#:** `### GameDataSend: CMDID 123`
|
||||
- **Purpose:** Stops the skill execution
|
||||
- **Status:** ✅ Both handle correctly
|
||||
- **C++ Log:** `CECHPWork::WORK_FLASHMOVE priority=2 killed`
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ Unhandled Protocols in C# (But Received)
|
||||
|
||||
#### 1. SET_MOVE_STAMP (205)
|
||||
- **C++:** `SERVER - SET_MOVE_STAMP(205), size=2`
|
||||
- **C#:** `### GameDataSend: CMDID 205 (Unhandled CMDID 205 (payloadBytes=2))`
|
||||
- **Purpose:** Sets movement synchronization stamp
|
||||
- **Status:** ⚠️ C# receives but doesn't handle
|
||||
- **Impact:** May cause movement desynchronization issues
|
||||
|
||||
#### 2. UPDATE_EXT_STATE (124)
|
||||
- **C++:** `SERVER - UPDATE_EXT_STATE(124), size=28`
|
||||
- **C#:** `### GameDataSend: Unhandled CMDID 124 (payloadBytes=28)`
|
||||
- **Purpose:** Updates extended state (buffs, debuffs, effects)
|
||||
- **Status:** ⚠️ C# receives but doesn't handle
|
||||
- **Impact:** Extended states may not update correctly
|
||||
|
||||
#### 3. ICON_STATE_NOTIFY (125)
|
||||
- **C++:** `SERVER - ICON_STATE_NOTIFY(125), size=10` (first) / `size=8` (second)
|
||||
- **C#:** `### GameDataSend: Unhandled CMDID 125 (payloadBytes=10)` / `(payloadBytes=8)`
|
||||
- **Purpose:** Notifies icon state changes (UI updates)
|
||||
- **Status:** ⚠️ C# receives but doesn't handle
|
||||
- **Impact:** UI icons may not reflect correct state
|
||||
|
||||
---
|
||||
|
||||
## Protocol Sequence Timeline
|
||||
|
||||
### C++ Timeline (EC.log)
|
||||
```
|
||||
[17:10:03.501] CLIENT - CAST_POS_SKILL(89)
|
||||
[17:10:03.565] SERVER - OBJECT_MOVE(15) × 9
|
||||
[17:10:03.565] SERVER - OBJECT_STOP_MOVE(35)
|
||||
[17:10:03.565] SERVER - SET_MOVE_STAMP(205) ← 64ms delay
|
||||
[17:10:03.565] SERVER - OBJECT_CAST_POS_SKILL(204)
|
||||
[17:10:03.565] SERVER - SKILL_PERFORM(88)
|
||||
[17:10:03.565] SERVER - SET_COOLDOWN(198)
|
||||
[17:10:03.565] SERVER - COMBO_SKILL_PREPARE(394)
|
||||
[17:10:03.565] SERVER - ENCHANT_RESULT(139)
|
||||
[17:10:03.766] SERVER - SELF_INFO_00(38) ← 265ms delay
|
||||
[17:10:03.766] SERVER - UPDATE_EXT_STATE(124)
|
||||
[17:10:03.766] SERVER - ICON_STATE_NOTIFY(125)
|
||||
[17:10:04.567] SERVER - OBJECT_MOVE(15) × 2 ← 1.066s delay
|
||||
[17:10:04.567] SERVER - OBJECT_STOP_MOVE(35)
|
||||
[17:10:04.567] SERVER - HOST_STOP_SKILL(123)
|
||||
[17:10:04.766] SERVER - UPDATE_EXT_STATE(124) ← 1.265s delay
|
||||
[17:10:04.766] SERVER - ICON_STATE_NOTIFY(125)
|
||||
```
|
||||
|
||||
### C# Timeline (SessionLog)
|
||||
```
|
||||
[16:58:15.286] CLIENT - CAST_POS_SKILL (via PROTOCOL_GAMEDATASEND)
|
||||
[16:58:15.402] SERVER - CMDID 205 (Unhandled) ← 116ms delay
|
||||
[16:58:15.403] SERVER - CMDID 204
|
||||
[16:58:15.404] SERVER - CMDID 88
|
||||
[16:58:15.406] SERVER - CMDID 198
|
||||
[16:58:15.407] SERVER - CMDID 394
|
||||
[16:58:15.408] SERVER - CMDID 139
|
||||
[16:58:15.409] SERVER - CMDID 38
|
||||
[16:58:15.410] SERVER - CMDID 124 (Unhandled)
|
||||
[16:58:15.411] SERVER - CMDID 125 (Unhandled)
|
||||
[16:58:15.588] SERVER - CMDID 15 × 2 ← 302ms delay
|
||||
[16:58:15.591] SERVER - CMDID 35
|
||||
[16:58:16.388] SERVER - CMDID 38 ← 1.102s delay
|
||||
[16:58:16.389] SERVER - CMDID 124 (Unhandled)
|
||||
[16:58:16.391] SERVER - CMDID 125 (Unhandled)
|
||||
[16:58:16.392] SERVER - CMDID 123
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Findings
|
||||
|
||||
### ✅ What Works the Same
|
||||
1. **Core skill protocols** (204, 88, 198, 394, 139) are handled identically
|
||||
2. **Movement protocols** (15, 35) work the same
|
||||
3. **Player info** (38) updates correctly
|
||||
4. **Skill stop** (123) is handled
|
||||
|
||||
### ⚠️ Missing Handlers in C#
|
||||
1. **SET_MOVE_STAMP (205)** - May cause movement sync issues
|
||||
2. **UPDATE_EXT_STATE (124)** - Extended states (buffs/debuffs) may not update
|
||||
3. **ICON_STATE_NOTIFY (125)** - UI icon states may be incorrect
|
||||
|
||||
### 📊 Protocol Count Comparison
|
||||
|
||||
| Protocol | C++ Count | C# Count | Difference |
|
||||
|----------|-----------|---------|------------|
|
||||
| OBJECT_MOVE (15) | 11 | 8 | C++ has 3 more |
|
||||
| OBJECT_STOP_MOVE (35) | 2 | 1 | C++ has 1 more |
|
||||
| SET_MOVE_STAMP (205) | 1 | 1 (unhandled) | Same count |
|
||||
| OBJECT_CAST_POS_SKILL (204) | 1 | 1 | ✅ Same |
|
||||
| SKILL_PERFORM (88) | 1 | 1 | ✅ Same |
|
||||
| SET_COOLDOWN (198) | 1 | 1 | ✅ Same |
|
||||
| COMBO_SKILL_PREPARE (394) | 1 | 1 | ✅ Same |
|
||||
| ENCHANT_RESULT (139) | 1 | 1 | ✅ Same |
|
||||
| SELF_INFO_00 (38) | 1 | 1 | ✅ Same |
|
||||
| UPDATE_EXT_STATE (124) | 2 | 2 (unhandled) | Same count |
|
||||
| ICON_STATE_NOTIFY (125) | 2 | 2 (unhandled) | Same count |
|
||||
| HOST_STOP_SKILL (123) | 1 | 1 | ✅ Same |
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
### High Priority
|
||||
1. **Implement SET_MOVE_STAMP (205) handler** in C#
|
||||
- Critical for movement synchronization
|
||||
- May cause desync between client and server positions
|
||||
|
||||
2. **Implement UPDATE_EXT_STATE (124) handler** in C#
|
||||
- Important for buff/debuff/effect state management
|
||||
- Affects gameplay mechanics
|
||||
|
||||
3. **Implement ICON_STATE_NOTIFY (125) handler** in C#
|
||||
- Affects UI correctness
|
||||
- Lower priority but improves user experience
|
||||
|
||||
### Medium Priority
|
||||
1. **Investigate OBJECT_MOVE count difference**
|
||||
- C++ receives 11 OBJECT_MOVE commands
|
||||
- C# receives 8 OBJECT_MOVE commands
|
||||
- May indicate timing or network differences
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
**Overall Status:** ✅ **Most protocols match** - The core skill casting flow works correctly in both implementations.
|
||||
|
||||
**Issues Found:** ⚠️ **3 unhandled protocols in C#** that may cause:
|
||||
- Movement synchronization problems (SET_MOVE_STAMP)
|
||||
- Extended state update issues (UPDATE_EXT_STATE)
|
||||
- UI icon state inconsistencies (ICON_STATE_NOTIFY)
|
||||
|
||||
The server sends the same protocols to both clients, but C# needs to implement handlers for commands 205, 124, and 125 to achieve full feature parity with the C++ client.
|
||||
@@ -0,0 +1,532 @@
|
||||
# Flashmove Skill Casting Issue - Debug Summary
|
||||
|
||||
## Problem Description
|
||||
|
||||
**Issue:** After casting a flashmove skill in C#, when attempting to cast another skill immediately after, the client receives error message ID 20 (FIXMSG_NEEDITEM - "Need item"), preventing skill casting. This issue does NOT occur in the C++ version.
|
||||
|
||||
**Error Code 20:** Returned by `CheckSkillCastCondition()` when a skill requires an item but the player doesn't have it.
|
||||
|
||||
## Investigation Status
|
||||
|
||||
### What We've Done
|
||||
|
||||
1. **Added comprehensive logging to C#** (`CECHostPlayer.Skill.cs` and `CECHostPlayer.Combat.cs`)
|
||||
- Entry logging in `ApplySkillShortcut()` with full state information
|
||||
- Detailed logging in `CastSkill()` for network commands and m_pPrepSkill state changes
|
||||
- Detailed logging in `CheckSkillCastCondition()` for item requirements
|
||||
- Server response logging in `OnMsgPlayerCastSkill()`
|
||||
- Enhanced logging to track m_pPrepSkill state transitions (set/clear operations)
|
||||
|
||||
2. **Added matching logging to C++** (`EC_HostPlayer.cpp` and `EC_HostMsg.cpp`)
|
||||
- Same comprehensive logging to enable direct comparison
|
||||
|
||||
3. **Removed non-C++ code** (2026-03-09)
|
||||
- Removed safeguard code that cleared stale m_pPrepSkill at start of ApplySkillShortcut()
|
||||
- This code didn't exist in C++ and was causing divergence from original behavior
|
||||
- C# code now matches C++ behavior exactly for m_pPrepSkill handling
|
||||
|
||||
### Key Files Modified
|
||||
|
||||
#### C# Files:
|
||||
- `e:\Projects\perfect-world-unity\Assets\Scripts\CECHostPlayer.Skill.cs`
|
||||
- `ApplySkillShortcut()` - Lines ~445-744
|
||||
- `CastSkill()` - Lines ~751-1002
|
||||
- `CheckSkillCastCondition()` - Lines ~1208-1222
|
||||
|
||||
- `e:\Projects\perfect-world-unity\Assets\Scripts\CECHostPlayer.Combat.cs`
|
||||
- `OnMsgPlayerCastSkill()` - Lines ~208-660
|
||||
- `OBJECT_CAST_SKILL` handler - Lines ~219-285
|
||||
- `OBJECT_CAST_POS_SKILL` handler - Lines ~414-538
|
||||
- `SKILL_PERFORM` handler - Lines ~287-305
|
||||
|
||||
#### C++ Files:
|
||||
- `perfect-world-source/perfect-world-source/CElement/CElementClient/EC_HostPlayer.cpp`
|
||||
- `ApplySkillShortcut()` - Lines ~2468-2800
|
||||
- `CastSkill()` - Lines ~6037-6250
|
||||
- `CheckSkillCastCondition()` - Lines ~5779-5851
|
||||
|
||||
- `perfect-world-source/perfect-world-source/CElement/CElementClient/EC_HostMsg.cpp`
|
||||
- `OnMsgPlayerCastSkill()` - Lines ~5865-6151
|
||||
- `OBJECT_CAST_SKILL` handler - Lines ~5878-5932
|
||||
- `OBJECT_CAST_POS_SKILL` handler - Lines ~6038-6151
|
||||
- `SKILL_PERFORM` handler - Lines ~5933-5941
|
||||
|
||||
## Key Differences Found
|
||||
|
||||
### Flashmove Skill Handling
|
||||
|
||||
**C++ Behavior:**
|
||||
- When `OBJECT_CAST_POS_SKILL` is received, creates `WORK_FLASHMOVE` work
|
||||
- `m_pPrepSkill` is cleared in `SKILL_PERFORM` message (after skill execution)
|
||||
- Flashmove skills use `c2s_CmdCastPosSkill()` network command
|
||||
|
||||
**C# Behavior:**
|
||||
- Same as C++ for most parts
|
||||
- **Potential Issue:** `m_pPrepSkill` might not be cleared properly when `OBJECT_CAST_POS_SKILL` is received
|
||||
|
||||
### Network Commands
|
||||
|
||||
**Flashmove Skills:**
|
||||
- Self-target flashmove: `c2s_CmdCastPosSkill(skillID, position, byPVPMask, 0, NULL)`
|
||||
- Target flashmove: `c2s_CmdCastPosSkill(skillID, position, byPVPMask, 1, &idTarget)`
|
||||
|
||||
**Regular Skills:**
|
||||
- `c2s_CmdCastSkill(skillID, byPVPMask, targetCount, targets[])`
|
||||
|
||||
**Instant Skills:**
|
||||
- `c2s_CmdCastInstantSkill(skillID, byPVPMask, targetCount, targets[])`
|
||||
|
||||
## Logging Format
|
||||
|
||||
All logs use the prefix `[SKILL_CAST_DEBUG]` for easy filtering.
|
||||
|
||||
### Key Log Points to Compare
|
||||
|
||||
1. **ApplySkillShortcut Entry:**
|
||||
```
|
||||
[SKILL_CAST_DEBUG] ApplySkillShortcut: Entry, skillID=X, IsSpellingMagic=Y, m_pPrepSkill=Z, m_pCurSkill=W, IsFlashMoving=V
|
||||
```
|
||||
|
||||
2. **CheckSkillCastCondition:**
|
||||
```
|
||||
[SKILL_CAST_DEBUG] CheckSkillCastCondition: Entry, skillID=X
|
||||
[SKILL_CAST_DEBUG] CheckSkillCastCondition: Item check, skillID=X, idItem=Y, itemTotalNum=Z
|
||||
[SKILL_CAST_DEBUG] CheckSkillCastCondition: ERROR 20 - Need item, skillID=X, requiredItem=Y, have=Z
|
||||
```
|
||||
|
||||
3. **CastSkill Network Commands:**
|
||||
```
|
||||
[SKILL_CAST_DEBUG] CastSkill: Sending c2s_CmdCastPosSkill (flashmove self), skillID=X, pos=(x,y,z), byPVPMask=Y
|
||||
[SKILL_CAST_DEBUG] CastSkill: Sending c2s_CmdCastSkill (regular), skillID=X, target=Y, byPVPMask=Z
|
||||
```
|
||||
|
||||
4. **Server Responses:**
|
||||
```
|
||||
[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: Received OBJECT_CAST_POS_SKILL, skillID=X, m_pPrepSkill=Y, m_pCurSkill=Z
|
||||
[SKILL_CAST_DEBUG] OnMsgPlayerCastSkill: Received SKILL_PERFORM, m_pPrepSkill=X (clearing)
|
||||
```
|
||||
|
||||
## What to Check When Testing
|
||||
|
||||
### After Flashmove, Before Next Skill:
|
||||
|
||||
1. **State Variables:**
|
||||
- Is `m_pPrepSkill` still set? (Should be NULL)
|
||||
- Is `IsSpellingMagic()` returning true? (Should be false)
|
||||
- Is `IsFlashMoving()` returning true? (Should be false)
|
||||
- Is `m_pCurSkill` set? (May or may not be set)
|
||||
|
||||
2. **Server Response:**
|
||||
- Did `OBJECT_CAST_POS_SKILL` arrive?
|
||||
- Was `WORK_FLASHMOVE` created?
|
||||
- Was `m_pPrepSkill` cleared when it should be?
|
||||
|
||||
3. **Next Skill Cast:**
|
||||
- What does `CheckSkillCastCondition()` return? (Should be 0, not 20)
|
||||
- If error 20: What item is required? What's the current count?
|
||||
- Are network command parameters correct?
|
||||
|
||||
## Potential Root Causes
|
||||
|
||||
1. **m_pPrepSkill Not Cleared:**
|
||||
- In C++, `m_pPrepSkill` is cleared in `SKILL_PERFORM` message
|
||||
- In C#, might not be cleared properly after flashmove
|
||||
- This could cause the next skill to check conditions with wrong skill reference
|
||||
|
||||
2. **Work State Issue:**
|
||||
- `WORK_FLASHMOVE` might not be finishing properly
|
||||
- `IsSpellingMagic()` or `IsFlashMoving()` might return true when they shouldn't
|
||||
- This could block the next skill cast
|
||||
|
||||
3. **Item Requirement Check:**
|
||||
- `CheckSkillCastCondition()` might be checking item requirements incorrectly
|
||||
- The flashmove skill's item requirement might be incorrectly applied to the next skill
|
||||
|
||||
4. **Network Command Timing:**
|
||||
- Client might be sending the next skill command before server confirms flashmove
|
||||
- Server might reject it due to state mismatch
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Run Test:**
|
||||
- Cast flashmove skill
|
||||
- Immediately cast another skill
|
||||
- Collect logs from both C++ and C# versions
|
||||
- **Note:** Logs from SessionLog_2026-03-09_10-48-05.txt show error 20 still occurs
|
||||
|
||||
2. **Compare Logs:**
|
||||
- Filter by `[SKILL_CAST_DEBUG]`
|
||||
- Compare state variables at each step
|
||||
- Identify where C# behavior diverges from C++
|
||||
- **Key:** Check if `[SKILL_CAST_DEBUG]` logs are appearing (they weren't in the test log)
|
||||
|
||||
3. **Focus Areas:**
|
||||
- When is `m_pPrepSkill` cleared in C# vs C++? (Should be immediately after sending flashmove command)
|
||||
- What is the state when `CheckSkillCastCondition()` returns error 20?
|
||||
- Are network commands sent with correct parameters?
|
||||
- **NEW:** Verify that debug logs are actually being written (may need to check Unity console or log file location)
|
||||
|
||||
4. **Server-Side Investigation:**
|
||||
- Error 20 comes from server, not client `CheckSkillCastCondition()`
|
||||
- Server might be checking wrong skill's item requirement
|
||||
- Server might be receiving incorrect skill ID in network command
|
||||
- Need to verify what skill ID the server receives vs what client sends
|
||||
|
||||
## Code References
|
||||
|
||||
### CheckSkillCastCondition Error 20:
|
||||
```cpp
|
||||
// C++: EC_HostPlayer.cpp line ~5781-5785
|
||||
int idItem = pSkill->GetRequiredItem();
|
||||
if (idItem > 0 && GetPack()->GetItemTotalNum(idItem) <= 0)
|
||||
{
|
||||
return 20; // Need item
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// C#: CECHostPlayer.Skill.cs line ~1161-1165
|
||||
int idItem = pSkill.SkillCore.GetItemCost();
|
||||
if (idItem > 0 && GetPack().GetItemTotalNum(idItem) <= 0)
|
||||
{
|
||||
return 20; // Need item
|
||||
}
|
||||
```
|
||||
|
||||
### Flashmove Skill Casting:
|
||||
```cpp
|
||||
// C++: EC_HostPlayer.cpp line ~6106-6130
|
||||
else if (m_pPrepSkill->GetType() == CECSkill::TYPE_FLASHMOVE)
|
||||
{
|
||||
// ... flashmove logic ...
|
||||
g_pGame->GetGameSession()->c2s_CmdCastPosSkill(...);
|
||||
m_pPrepSkill = NULL;
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// C#: CECHostPlayer.Skill.cs line ~831-860
|
||||
else if (m_pPrepSkill.GetType() == (int)CECSkill.SkillType.TYPE_FLASHMOVE)
|
||||
{
|
||||
// ... flashmove logic ...
|
||||
UnityGameSession.c2s_CmdCastPosSkill(...);
|
||||
m_pPrepSkill = null;
|
||||
}
|
||||
```
|
||||
|
||||
### Server Response Handling:
|
||||
```cpp
|
||||
// C++: EC_HostMsg.cpp line ~6038-6147
|
||||
case OBJECT_CAST_POS_SKILL:
|
||||
{
|
||||
// Creates WORK_FLASHMOVE
|
||||
CECHPWorkFMove* pWork = (CECHPWorkFMove*)m_pWorkMan->CreateWork(CECHPWork::WORK_FLASHMOVE);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// C#: CECHostPlayer.Combat.cs line ~414-534
|
||||
case CommandID.OBJECT_CAST_POS_SKILL:
|
||||
{
|
||||
// Creates WORK_FLASHMOVE
|
||||
CECHPWorkFMove pWork = (CECHPWorkFMove)m_pWorkMan.CreateWork(CECHPWork.Host_work_ID.WORK_FLASHMOVE);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- All logging uses `[SKILL_CAST_DEBUG]` prefix for easy filtering
|
||||
- Error 20 = FIXMSG_NEEDITEM = "Need item" error
|
||||
- Flashmove skills are TYPE_FLASHMOVE skill type
|
||||
- `m_pPrepSkill` should be cleared after sending network command (for instant/flashmove) or on server response (for regular skills)
|
||||
- `m_pCurSkill` is set when server responds with OBJECT_CAST_SKILL or OBJECT_CAST_POS_SKILL
|
||||
|
||||
## Questions to Answer
|
||||
|
||||
1. When exactly does error 20 occur? (Which skill triggers it?)
|
||||
2. What item is required? (Check the logs for `requiredItem` value)
|
||||
3. Does the player actually have the item? (Check `itemTotalNum` in logs)
|
||||
4. Is `m_pPrepSkill` still set to the flashmove skill when checking the next skill?
|
||||
5. Is `CheckSkillCastCondition()` being called with the correct skill object?
|
||||
6. **NEW:** Why are `[SKILL_CAST_DEBUG]` logs not appearing in SessionLog_2026-03-09_10-48-05.txt?
|
||||
7. **NEW:** Is the error coming from client-side `CheckSkillCastCondition()` or server-side validation?
|
||||
|
||||
## Recent Changes (2026-03-09)
|
||||
|
||||
### Removed Non-C++ Code
|
||||
- **File:** `Assets/Scripts/CECHostPlayer.Skill.cs`
|
||||
- **Lines removed:** 453-459 (safeguard code that cleared stale m_pPrepSkill)
|
||||
- **Reason:** This code didn't exist in C++ and was causing divergence from original behavior
|
||||
- **Status:** ✅ Removed - C# now matches C++ behavior
|
||||
|
||||
### Enhanced Logging
|
||||
- **File:** `Assets/Scripts/CECHostPlayer.Skill.cs`
|
||||
- Added detailed logging for m_pPrepSkill state transitions:
|
||||
- When m_pPrepSkill is set in ApplySkillShortcut()
|
||||
- When m_pPrepSkill is cleared in CastSkill() for flashmove skills
|
||||
- Which skill ID is being checked in CheckSkillCastCondition()
|
||||
- **File:** `Assets/Scripts/CECHostPlayer.Combat.cs`
|
||||
- Added logging in SKILL_PERFORM handler to track when m_pPrepSkill is cleared
|
||||
|
||||
### Current Status
|
||||
- ✅ Code now matches C++ behavior for m_pPrepSkill handling
|
||||
- ❌ Bug still exists (error 20 when casting skill after flashmove)
|
||||
- ⚠️ Debug logs not appearing in test session log (need to verify log output location)
|
||||
- 🔍 Next: Compare C++ and C# logs side-by-side to find root cause
|
||||
|
||||
## Root Cause Analysis (2026-03-09)
|
||||
|
||||
### Key Finding: Error 20 is Server-Side, Not Client-Side
|
||||
|
||||
**Critical Discovery:** The error 20 (`FIXMSG_NEEDITEM`) is coming from the **SERVER**, not from client-side `CheckSkillCastCondition()`.
|
||||
|
||||
**Evidence from logs:**
|
||||
- Unity log (SessionLog_2026-03-09_11-16-28.txt):
|
||||
- Line 535: `CAST_SKILL` sent
|
||||
- Line 536: `### GameDataSend: ERROR_MESSAGE: 20` ← **Server response**
|
||||
- Line 537: `### GameDataSend: ERROR_MESSAGE parsed iMessage=20`
|
||||
|
||||
- C++ log (EC.log):
|
||||
- Line 5448: Flashmove skill (skillID=58) cast
|
||||
- Line 5472: Server responds with `OBJECT_CAST_POS_SKILL` and `SKILL_PERFORM` (almost immediately)
|
||||
- Line 6208: Next skill (skillID=1) cast successfully - **NO ERROR**
|
||||
|
||||
### The Problem
|
||||
|
||||
**Timing Issue:** In the Unity version, the next skill is being cast **before the server has fully processed the flashmove skill**.
|
||||
|
||||
**Sequence in Unity:**
|
||||
1. `11:16:57.856` - `CAST_POS_SKILL` sent (flashmove)
|
||||
2. `11:17:00.858` - `CAST_SKILL` sent (next skill) ← **3 seconds later**
|
||||
3. `11:17:00.972` - Server returns `ERROR_MESSAGE: 20`
|
||||
|
||||
**Sequence in C++ (working):**
|
||||
1. `10:35:21.207` - Flashmove skill cast
|
||||
2. `10:35:21.276` - Server responds with `OBJECT_CAST_POS_SKILL` and `SKILL_PERFORM` ← **Immediate response**
|
||||
3. `10:35:24.438` - Next skill cast successfully ← **3+ seconds later, but AFTER server confirmed flashmove**
|
||||
|
||||
### Why This Happens
|
||||
|
||||
The server needs to:
|
||||
1. Process the flashmove skill
|
||||
2. Send `OBJECT_CAST_POS_SKILL` back to client
|
||||
3. Send `SKILL_PERFORM` to clear the skill state
|
||||
4. Only then is it ready for the next skill
|
||||
|
||||
If the next skill is sent **before** the server has sent `SKILL_PERFORM`, the server may still be in a state where it thinks the flashmove skill is active, causing it to check the wrong skill's item requirements.
|
||||
|
||||
### Missing Server Response in Unity Log
|
||||
|
||||
**Critical Observation:** The Unity log shows:
|
||||
- `CAST_POS_SKILL` sent at `11:16:57.856`
|
||||
- But **NO** `OBJECT_CAST_POS_SKILL` or `SKILL_PERFORM` response from server before the next skill is cast
|
||||
|
||||
This suggests either:
|
||||
1. The server response is not being received/logged
|
||||
2. The server response handler is not being called
|
||||
3. There's a network timing issue
|
||||
|
||||
### Solution
|
||||
|
||||
**Option 1: Wait for Server Confirmation**
|
||||
- Don't allow casting the next skill until `SKILL_PERFORM` is received for the flashmove skill
|
||||
- This matches C++ behavior where the client waits for server confirmation
|
||||
|
||||
**Option 2: Check Server State**
|
||||
- Before casting the next skill, verify that `m_pPrepSkill` is null (which should be set after `SKILL_PERFORM`)
|
||||
- If `m_pPrepSkill` is still set, wait for `SKILL_PERFORM` before allowing next skill
|
||||
|
||||
**Option 3: Server-Side Fix**
|
||||
- If the error is truly server-side, the server should handle the case where a new skill is sent while a flashmove is still processing
|
||||
- However, since C++ works fine, this is likely a client-side timing issue
|
||||
|
||||
### Recommended Fix
|
||||
|
||||
**Immediate Fix:** Add a check in `ApplySkillShortcut()` or `CastSkill()` to prevent casting a new skill if:
|
||||
- `m_pPrepSkill` is still set (waiting for server confirmation)
|
||||
- OR `IsFlashMoving()` returns true (flashmove work is still active)
|
||||
|
||||
This will ensure the client waits for server confirmation before allowing the next skill cast, matching C++ behavior.
|
||||
|
||||
## Root Cause Found (2026-03-09)
|
||||
|
||||
### The Missing Check
|
||||
|
||||
**Problem:** The C# code was missing the `CanCastSkillImmediately()` check that exists in C++.
|
||||
|
||||
**C++ Code (EC_HostPlayer.cpp, line 2688-2694):**
|
||||
```cpp
|
||||
// Check if skill can be cast immediately (blocks casting during flash move at PRIORITY_2)
|
||||
if (!m_pWorkMan->CanCastSkillImmediately(pSkill->GetSkillID()))
|
||||
{
|
||||
bool hasWorkOnPriority2 = m_pWorkMan->HasWorkRunningOnPriority(CECHPWorkMan::PRIORITY_2);
|
||||
a_LogOutput(1, "[SKILL_CAST_DEBUG] ApplySkillShortcut: BLOCKED - CanCastSkillImmediately returned false, skillID=%d, IsSpellingMagic=%d, HasWorkOnPriority2=%d",
|
||||
idSkill, IsSpellingMagic() ? 1 : 0, hasWorkOnPriority2 ? 1 : 0);
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
**C# Code (BEFORE FIX):**
|
||||
- Missing this check in the main casting path
|
||||
- Only checked `ReadyToCast()` but not `CanCastSkillImmediately()`
|
||||
- This allowed casting a new skill while flashmove work (PRIORITY_2) was still active
|
||||
|
||||
**Why This Causes Error 20:**
|
||||
1. Flashmove skill is cast → `WORK_FLASHMOVE` created at PRIORITY_2
|
||||
2. `m_pPrepSkill` is cleared immediately after sending network command (correct)
|
||||
3. User tries to cast next skill → `CanCastSkillImmediately()` should return false (blocking it)
|
||||
4. **BUT** the check was missing, so the skill is sent to server
|
||||
5. Server receives the skill while flashmove is still processing
|
||||
6. Server checks the wrong skill's item requirement → returns ERROR 20
|
||||
|
||||
### Fix Applied
|
||||
|
||||
**File:** `Assets/Scripts/CECHostPlayer.Skill.cs`
|
||||
**Location:** Line ~683 (after `ReadyToCast()` check, before setting `m_pPrepSkill`)
|
||||
|
||||
**Added:**
|
||||
```csharp
|
||||
// Check if skill can be cast immediately (blocks casting during flash move at PRIORITY_2)
|
||||
// 检查技能是否可以立即施放(阻止在PRIORITY_2的闪移工作期间施放)
|
||||
if (!m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID()))
|
||||
{
|
||||
bool hasWorkOnPriority2 = m_pWorkMan.HasWorkRunningOnPriority(CECHPWorkMan.Priority.PRIORITY_2);
|
||||
Debug.Log($"[SKILL_CAST_DEBUG] ApplySkillShortcut: BLOCKED - CanCastSkillImmediately returned false, skillID={idSkill}, " +
|
||||
$"IsSpellingMagic={IsSpellingMagic()}, HasWorkOnPriority2={hasWorkOnPriority2}");
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
This check prevents casting a new skill while `WORK_FLASHMOVE` (or any other PRIORITY_2 work) is still active, matching C++ behavior exactly.
|
||||
|
||||
### Status
|
||||
|
||||
- ✅ **Root cause identified:** Missing `CanCastSkillImmediately()` check
|
||||
- ✅ **Fix applied:** Added the check to match C++ behavior
|
||||
- ✅ **Enhanced fix:** Added `IsFlashMoving()` check as additional safeguard
|
||||
- ❌ **Still occurring:** Error 20 still happens in test (SessionLog_2026-03-09_11-43-06.txt)
|
||||
- 🔍 **Investigation:** The check may not be sufficient - client-side flashmove work might finish before server processes it
|
||||
|
||||
### Updated Fix (2026-03-09)
|
||||
|
||||
**Enhanced Check:** Added `IsFlashMoving()` check in addition to `CanCastSkillImmediately()`:
|
||||
|
||||
```csharp
|
||||
// Check if skill can be cast immediately (blocks casting during flash move at PRIORITY_2)
|
||||
// Also check IsFlashMoving() as an additional safeguard since client-side work might finish
|
||||
// before server processes the flashmove
|
||||
if (IsFlashMoving() || !m_pWorkMan.CanCastSkillImmediately(pSkill.GetSkillID()))
|
||||
{
|
||||
// BLOCKED
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
**Why this is needed:** The client-side flashmove work (`WORK_FLASHMOVE`) might finish before the server processes the flashmove command. When this happens:
|
||||
- `CanCastSkillImmediately()` might return `true` (work is done on client)
|
||||
- But server is still processing the flashmove
|
||||
- Next skill sent to server → server checks wrong skill → ERROR 20
|
||||
|
||||
**Note:** Debug logs (`[SKILL_CAST_DEBUG]`) are not appearing in session log files. They may be going to Unity console instead. This makes it difficult to verify if the check is being executed.
|
||||
|
||||
## Additional Issue: "Distance Too Far" Error After Flashmove (2026-03-09)
|
||||
|
||||
### Problem
|
||||
After casting a flashmove skill, normal attacks and skill casts fail with "distance too far" error (ERROR_MESSAGE: 2), even though the target should be in range.
|
||||
|
||||
### Root Cause
|
||||
**The Real Issue:** `CanTouchTarget()` and distance calculation functions were using `transform.position` (the visual position) instead of the server-tracked position (`GetLastSevPos()`).
|
||||
|
||||
**What Happens:**
|
||||
1. When flashmove happens, `SetHostLastPos()` and `SetLastSevPos()` ARE updated immediately (this was already fixed)
|
||||
2. BUT `CanTouchTarget()` uses `transform.position` which is NOT updated immediately - it's moved gradually by `WORK_FLASHMOVE` work
|
||||
3. So distance checks use the OLD visual position, while server uses the tracked position (which was updated)
|
||||
4. Client thinks player is still at old position → distance check fails → ERROR 2 from server
|
||||
|
||||
### Fix Applied (2026-03-09)
|
||||
|
||||
**File 1:** `Assets/Scripts/CECHostPlayer.cs`
|
||||
**Location:** Line ~1534 (CanTouchTarget overload)
|
||||
|
||||
**Changed:**
|
||||
```csharp
|
||||
// BEFORE (WRONG):
|
||||
A3DVECTOR3 vector = new A3DVECTOR3(playerTransform.position.x, playerTransform.position.y,
|
||||
playerTransform.position.z);
|
||||
|
||||
// AFTER (CORRECT):
|
||||
// Use server-tracked position instead of visual position for distance checks
|
||||
A3DVECTOR3 vector = EC_Utility.ToA3DVECTOR3(m_MoveCtrl.GetLastSevPos());
|
||||
```
|
||||
|
||||
**File 2:** `Assets/Scripts/CECHostPlayer.Combat.cs`
|
||||
**Location:** Line ~791 (NormalAttackObject)
|
||||
|
||||
**Changed:**
|
||||
```csharp
|
||||
// BEFORE (WRONG):
|
||||
A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(transform.position);
|
||||
|
||||
// AFTER (CORRECT):
|
||||
// Use server-tracked position instead of visual position for distance checks
|
||||
A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(m_MoveCtrl.GetLastSevPos());
|
||||
```
|
||||
|
||||
**File 3:** `Assets/Scripts/CECHostPlayer.Skill.cs`
|
||||
**Location:** Line ~1102 (CastSkill logging)
|
||||
|
||||
**Changed:**
|
||||
```csharp
|
||||
// BEFORE (WRONG):
|
||||
A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(transform.position);
|
||||
|
||||
// AFTER (CORRECT):
|
||||
// Use server-tracked position instead of visual position for accurate distance checks
|
||||
A3DVECTOR3 vHostPos = EC_Utility.ToA3DVECTOR3(m_MoveCtrl.GetLastSevPos());
|
||||
```
|
||||
|
||||
**Why this works:**
|
||||
- `GetLastSevPos()` returns the server-tracked position, which is updated immediately when flashmove happens
|
||||
- `transform.position` is the visual position, which is moved gradually by `WORK_FLASHMOVE` work
|
||||
- Using the tracked position ensures distance checks match what the server sees
|
||||
|
||||
**C++ Comparison:**
|
||||
- **C++ (EC_HostPlayer.cpp, line 4106):** `CanTouchTarget(GetPos(), vTargetPos, ...)`
|
||||
- **C# (BEFORE FIX):** `CanTouchTarget(transform.position, ...)` ❌ Wrong - uses visual position
|
||||
- **C# (AFTER FIX):** `CanTouchTarget(GetLastSevPos(), ...)` ✅ Correct - uses server-tracked position
|
||||
|
||||
**Evidence from C++ Code:**
|
||||
|
||||
1. **Flashmove Position Update (EC_HostMsg.cpp, lines 6115-6178):**
|
||||
- For flashmove skills using `WORK_FLASHMOVE`, `SetPos()` is **NOT** called immediately
|
||||
- Only `SetHostLastPos()` and `SetLastSevPos()` are updated (via work system)
|
||||
- The work moves the player visually over time
|
||||
|
||||
2. **Position Comparison (EC_HostPlayer.cpp, line 4162):**
|
||||
```cpp
|
||||
if (GetPos() != m_MoveCtrl.GetLastSevPos())
|
||||
{
|
||||
m_MoveCtrl.SendMoveCmd(GetPos(), ...);
|
||||
}
|
||||
```
|
||||
This shows `GetPos()` and `GetLastSevPos()` **can be different**, but when they differ, the code sends a move command to sync them.
|
||||
|
||||
3. **Why C++ Works:**
|
||||
- `GetPos()` in C++ appears to be synchronized with server position through the work system
|
||||
- OR `GetPos()` might return `GetLastSevPos()` in certain contexts
|
||||
- OR the work system updates `GetPos()` during flashmove execution
|
||||
- The exact mechanism is unclear, but `CanTouchTarget(GetPos(), ...)` works correctly in C++
|
||||
|
||||
4. **Why C# Doesn't Work (Before Fix):**
|
||||
- `transform.position` in Unity is the visual position, updated gradually by `WORK_FLASHMOVE`
|
||||
- `GetPos()` in C# likely returns `transform.position` (visual position)
|
||||
- This causes distance checks to use the old position, failing immediately after flashmove
|
||||
|
||||
**Conclusion:** Using `GetLastSevPos()` directly in C# matches the server-tracked position that C++ `GetPos()` appears to use for distance checks, ensuring consistent behavior.
|
||||
|
||||
### Status
|
||||
- ✅ **Root cause identified:** Distance checks using visual position instead of tracked position
|
||||
- ✅ **Fix applied:** All distance checks now use `GetLastSevPos()` instead of `transform.position`
|
||||
- ⏳ **Testing needed:** Verify normal attacks and skill casts work correctly after flashmove
|
||||
Reference in New Issue
Block a user